继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

ArrayList浅析

sep123
关注TA
已关注
手记 1
粉丝 0
获赞 1

    一:ArrayList概述

 

          ArrayList是实现了List接口的动态数组,所谓动态数组就是它的大小可变。实现了所有可选列表操作,并允许包括null在内的所有元素。除了实现List接口以外,此类还提供一些方法来操作内部用来储存列表的数组的大小。

     每个ArrayList实例都有一个初始容量,该容量用来储存列表元素的数组大小。默认初始容量为10。

     

   /**

    * Default initial capacity.

    */

    private static final int DEFAULT_CAPACITY = 10;

     随着ArrayList中元素的增加,它的容量也会不断的自动增长。在每次添加新的元素是,ArrayList都会检查是否需要进行扩容操作。扩容操作带来数据的重新拷贝,所以如果我们知道具体业务数据量,在构造ArrayList时可以给ArrayList指定一个初始容量,这样会减少扩容时数据的拷贝问题。也可以在添加大量元素前,应用程序可以使用ensureCapacity操作来增加ArrayList实例的容量,这样可以减少递增式再分配的数量。

 if (minCapacity - elementData.length > 0)

     grow(minCapacity);

     二:数据结构

 private transient Object[] elementData;

     ArrayList底层采用数组实现。数组都有一个重大的缺陷,这就是从数组的中间位置删除一个元素要付出很大的代价,其原因是数组中处于被删除元素之后的所有元素都要向数组的前端移动。在数组中间位置插入一个元素也是如此(数据的copy)。

                                                                                                                                            。

                                                                                                                                              

transient为java关键字,为变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。      

三:源码分析  

     1.成员变量                                    

       ArrayList中的成员变量

 

    /**

     * Default initial capacity.

     */

    private static final int DEFAULT_CAPACITY = 10;

    /**

     * Shared empty array instance used for empty instances.

     */

    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**

     * The array buffer into which the elements of the ArrayList are stored.

     * The capacity of the ArrayList is the length of this array buffer. Any

     * empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to

     * DEFAULT_CAPACITY when the first element is added.

     */

    private transient Object[] elementData; 

    /**

     * The size of the ArrayList (the number of elements it contains).

     *

     * @serial

     */

    private int size;

                         

     基类AbstractList中的成员变量

   /**

     * The number of times this list has been <i>structurally modified</i>.

     * Structural modifications are those that change the size of the

     * list, or otherwise perturb it in such a fashion that iterations in

     * progress may yield incorrect results.

     *

     * <p>This field is used by the iterator and list iterator implementation

     * returned by the {@code iterator} and {@code listIterator} methods.

     * If the value of this field changes unexpectedly, the iterator (or list

     * iterator) will throw a {@code ConcurrentModificationException} in

     * response to the {@code next}, {@code remove}, {@code previous},

     * {@code set} or {@code add} operations.  This provides

     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in

     * the face of concurrent modification during iteration.

     *

     * <p><b>Use of this field by subclasses is optional.</b> If a subclass

     * wishes to provide fail-fast iterators (and list iterators), then it

     * merely has to increment this field in its {@code add(int, E)} and

     * {@code remove(int)} methods (and any other methods that it overrides

     * that result in structural modifications to the list).  A single call to

     * {@code add(int, E)} or {@code remove(int)} must add no more than

     * one to this field, or the iterators (and list iterators) will throw

     * bogus {@code ConcurrentModificationExceptions}.  If an implementation

     * does not wish to provide fail-fast iterators, this field may be

     * ignored.

     */

    protected transient int modCount = 0;

      2.操作

 

add(E e)操作,可能会使原容量扩充为1.5倍。且创建了新的数组,这意味着开辟了新的存储空间和带来的性能影响。


     public boolean add(E e) {

           // 1.所需最小容量,存储数组的数据长度+1(size是elementData数组包含的数据的长度,而elementData.length是数组的长度)

           ensureCapacityInternal(size + 1);

           elementData[size++] = e; // 9.将传入的数据,放入elementData队列末尾

           return true;

     }

     private void ensureCapacityInternal(int minCapacity) {

           if (elementData == EMPTY_ELEMENTDATA) {

                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 2.如果所需最小容量小于默认容量10,将最小容量赋值为默认容量10.

           }

           ensureExplicitCapacity(minCapacity);

     }

     private void ensureExplicitCapacity(int minCapacity) {

           modCount++; // 修改次数+1

           // overflow-conscious code

           if (minCapacity - elementData.length > 0) // 3.如果所需最小(默认)容量仍大于数组的长度,则进行扩容操作

                grow(minCapacity);

     }

     private void grow(int minCapacity) {

           // overflow-conscious code

           int oldCapacity = elementData.length; // 4.数组长度赋值给oldCapacity

           int newCapacity = oldCapacity + (oldCapacity >> 1);// 5.新容量=旧容量*1.5  oldCapacity >> 1 二进制算法 右移一位

           if (newCapacity - minCapacity < 0)// 6.如果新容量大于所需最小容量,则将新容量大小定义为最小容量的大小

                newCapacity = minCapacity;

           if (newCapacity - MAX_ARRAY_SIZE > 0)// 7.如果新的容量大小大于所允许的最大容量,对容量进行调整

                newCapacity = hugeCapacity(minCapacity);

           elementData = Arrays.copyOf(elementData, newCapacity); // 8.复制一个数组到新的数组,并赋值给elementData,意味着开辟新的空间和性能消耗

     }

     private static int hugeCapacity(int minCapacity) {

           if (minCapacity < 0) // overflow

                throw new OutOfMemoryError();

           return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;

     } 

     public static <T, U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {

           T[] copy = ((Object) newType == (Object) Object[].class) ? (T[]) new Object[newLength]

                      : (T[]) Array.newInstance(newType.getComponentType(), newLength);

           System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));

           return copy;

     }


add(int index, E element) 不会替换原位置的元素而是,插入一个新的元素,原插入为后面的元素后移。


      public void add(int index, E element) {

             rangeCheckForAdd(index); //入参校验

            

             ensureCapacityInternal(size + 1);  // 扩充容量

             System.arraycopy(elementData, index, elementData, index + 1, //复制数组

                              size - index);

             elementData[index] = element;

             size++;

     }

      //native 关键字 本地方法

     public static native void arraycopy(Object src,int srcPos,Object dest, int destPos,int length);



     public static void main(String[] args) {

           String[] a = "a b c d e f".split(" ");

           ArrayList<String> list = new ArrayList<>(Arrays.asList(a));

           System.out.println(list);

           list.add(2, "g");

           System.out.println(list);

     }

     //output:

     //[a,b,c,d,e,f]

     //[a,b,g,c,d,e,f]


remove(int index)


   public E remove(int index) {

        rangeCheck(index);

        modCount++;

        E oldValue = elementData(index);

        int numMoved = size - index - 1;  // numMoved = size - (index + 1)  复制长度

        if (numMoved > 0)

            System.arraycopy(elementData, index+1, elementData, index, numMoved);

        elementData[--size] = null; // clear to let GC do its work 等待垃圾回收器不定时回收

        return oldValue;

    }


模拟remove(int index )


     public static void main(String[] args) {

           String[] a = "a b c d e f".split(" ");

           ArrayList<String> list = new ArrayList<>(Arrays.asList(a));

           System.out.println(list);

           //remove a[2]

           System.arraycopy(a, 3, a, 2, a.length-3);

           a[5] = null;

           ArrayList<String> list1 = new ArrayList<>(Arrays.asList(a));

           System.out.println(list1);

     }

     //output:

     //[a, b, c, d, e, f]

     //[a, b, d, e, f, null]


remove(Object o) 移除遍历到的第一个符合的元素,支持null值,无fuck可说。


     public boolean remove(Object o) {

        if (o == null) {

            for (int index = 0; index < size; index++)

                if (elementData[index] == null) {

                    fastRemove(index);

                    return true;

                }

        } else {

            for (int index = 0; index < size; index++)

                if (o.equals(elementData[index])) {

                    fastRemove(index);

                    return true;

                }

        }

        return false;

    }

    private void fastRemove(int index) {

        modCount++;

        int numMoved = size - index - 1;

        if (numMoved > 0)

            System.arraycopy(elementData, index+1, elementData, index,

                             numMoved);

        elementData[--size] = null; // clear to let GC do its work

    }


contains(Object o) 遍历查找,返回遍历到的第一个符合元素的下标,否则返回-1,支持null值。


    public boolean contains(Object o) {

        return indexOf(o) >= 0;

    }

    public int indexOf(Object o) {

        if (o == null) {

            for (int i = 0; i < size; i++)

                if (elementData[i]==null)

                    return i;

        } else {

            for (int i = 0; i < size; i++)

                if (o.equals(elementData[i]))

                    return i;

        }

        return -1;

    }


get(int index) 返回存储数据的数组对应下标的元素。


    public E get(int index) {

        rangeCheck(index);

        return elementData(index);

    }

     

    E elementData(int index) {

        return (E) elementData[index];

    }


set(int index, E element) 覆盖指定位置的元素。


    public E set(int index, E element) {

        rangeCheck(index);

        E oldValue = elementData(index);

        elementData[index] = element;

        return oldValue;

    }


四:总结

  • ArrayLlist内部是由数组来实现的。在存放数据的数组长度不够时,会进行扩容,即增加数组长度。扩展为原来的1.5倍。

  • 由于是数组来实现,所以,优点是查找元素很快。可以通过下标查找元素,查找效率高。缺点是每次添加和删除元素都会进行大量的数组元素移动。长度不够会扩容。效率底下。

  • ArrayList每次的增、删、改操作都伴随着数组的复制和元素的移动。这意味着新的内存空间的开辟。

 

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP