一、ArrayList源码分析

1、ArrayList概述

1)ArrayList是可以动态增长和缩减的索引序列,是基于数组实现的List类,即动态数组。它是线程不安全的,允许元素为null。
2)该类封装了一个动态再分配的Object[]数组,每个类对象都有一个capacity属性,表示Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加。
3)因其底层数据结构是数组,所以它是占据一块连续的内存空间(容量就是数组的length),可以根据下标以O1的时间复杂度读写(改查)元素,因此时间效率很高,但它也有数组的缺点,空间效率不高
4)ArrayList中的扩容操作性能消耗比较大,若能提前知道数据规模,应通过public ArrayList(int initialCapacity) {}构造方法,指定集合的大小,构建实例对象,以减少扩容次数,提高性能
或扩容时,手动调用public void ensureCapacity(int minCapacity) {}方法扩容。不过该方法是ArrayList的API,不在List接口里,所以使用时需要用ArrayList实例对象调用,若是List对象则需要强转:((ArrayList)list).ensureCapacity(30);
5)ArrayList和Collection的关系:

一、ArrayList源码分析_第1张图片

2、ArrayList的数据结构

分析一个类的时候,数据结构往往是它的灵魂所在,理解底层的数据结构其实就理解了该类的实现思路,具体的实现细节再具体分析。
ArrayList的数据结构是:

说明:底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。对ArrayList类的实例的所有的操作底层都是基于数组的。

3、ArrayList源码分析

3.1继承结构和层次关系

public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable

ArrayList的继承结构:
ArrayList extends AbstractList
AbstractList extends AbstractCollection
所有类都继承Object类,所以ArrayList的继承结构就是上图所示。

注意:
1)为什么要先继承AbstractList,让AbstractList先实现List,而不是让ArrayList直接实现List
接口中全都是抽象方法,而抽象类中可以有抽象方法,还可以有具体的实现方法,先让抽象类AbstractList实现接口中一些通用的方法,再让具体的类,如ArrayList继承这个抽象类,拿到这些通用的方法,然后自己在实现一些特有的方法。这样让代码更简洁,将继承结构最底层的类中的通用方法都抽取出来,一起让抽象类实现,减少重复代码。
2)ArrayList实现了哪些接口?
①List接口:ArrayList的父类AbstractList也实现了List接口,为什么子类ArrayList还去实现一遍呢?开发这个collection 的作者Josh说,这其实是一个mistake,因为他写这代码的时候觉得这个会有用处,但是其实并没什么用,但因为没什么影响,就一直留到了现在。
②RandomAccess接口:是一个标记性接口,代表了其拥有随机快速访问的能力,可以以O(1)的时间复杂度去根据下标访问元素。
实现了该接口,使用普通的for循环来遍历性能更高,如ArrayList。
而没有实现该接口,使用Iterator迭代器性能更高,LinkedList。
所以它让我们知道用什么样的方式去获取数据性能更好。
③Cloneable接口:是一个标记性接口,实现了该接口,表示该对象能被克隆,能使用Object.clone()方法。没实现该接口调用Object.clone()方法就会抛出CloneNotSupportedException。
④Serializable接口:实现该序列化接口,表明该类可以被序列化。什么是序列化?简单的说,就是能够从类变成字节流传输,然后还能从字节流变成原来的类

3.2类中的属性

public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable
{
    // 版本号
    private static final long serialVersionUID = 8683452581122892189L;
    // 默认容量
    private static final int DEFAULT_CAPACITY = 10;
    // 空对象数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 默认空对象数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 元素数组
    transient Object[] elementData;
    // 实际元素大小,默认为0
    private int size;
    // 最大数组容量:2147483647-8
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    //modCount 是继承自AbstractList的属性,
    //记录了ArrayList结构性变化的次数,也就是集合长度变化次数。
    protected transient int modCount = 0;
}

3.3构造方法

ArrayList有三个构造方法:

1)无参构造方法

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    /**
     * Constructs an empty list with an initial capacity of ten.
     * 说明了默认会给10的大小,所以说调用无参构造,初始容量是10.
     */
    public ArrayList() {
       //默认第一行加上super(),即调用父类中的无参构造方法,是个空的构造方法
       /**DEFAULTCAPACITY_EMPTY_ELEMENTDATA:是个空的Object[]。
       将elementData初始化,空的Object[]会给默认大小10,此时还未赋值。*/
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

2)带初始容量的构造方法

    //空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
        //如果初始容量大于0,则新建一个长度为initialCapacity的Object数组.
        //注意这里并没有修改size(对比第三个构造函数)
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        //如果容量为0,直接赋为EMPTY_ELEMENTDATA
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

3)以其他集合为参数的构造方法

       /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection c) {
        //直接利用Collection.toArray()方法得到一个对象数组,并赋值给elementData
        elementData = c.toArray();
        //因为size代表的是集合元素数量,所以通过别的集合来构造ArrayList时,要给size赋值
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
            //这里是当c.toArray出错,没有返回Object[]时,
            //用Arrays.copyOf 来复制集合c中的元素到elementData数组中
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //如果集合c元素数量为0,则将空数组赋值给elementData 
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

小结:构造函数执行完毕,就是初始化一个数组elementData和数组实际元素个数size。
关于方法:Arrays.copyOf(elementData, size, Object[].class)是根据class的类型来决定是new 还是反射去构造一个泛型数组,同时利用native函数System.arraycopy,批量赋值元素至新数组中。
如下:

    public static  T[] copyOf(U[] original, int newLength, Class newType) {
        @SuppressWarnings("unchecked")
        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;
    }

//src:源对象;srcPos:源对象对象的起始位置
//dest:目标对象;destPost:目标对象的起始位置
//length:从起始位置往后复制的长度。
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

这些复制数组的方法都是浅拷贝,浅拷贝与深拷贝的区别可参考:
浅拷贝与深拷贝

3.4核心方法

3.4.1 增

1)boolean add(E e):默认直接在末尾添加元素

    public boolean add(E e) {
//确定内部容量是否够,size是数组中数据的个数,因为要添加一个元素,所以size+1。
//先判断size+1个数据,数组能否放得下,即在此方法中去判断是否数组.length是否够
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;//在数组末尾追加一个元素,并修改size
        return true;
    }
//确定内部容量的方法 
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

//此方法判断elementData是否为空,为空则返回默认容量(10)
//不为空则将传来的参数minCapacity原值(size+1)返回
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断初始化的elementData是不是空的数组,也就是没有长度
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//若为空,则minCapacity=size+1,其实就是等于1。
//空的数组没有长度就存放不了,所以就将minCapacity变成默认容量10
//但是此时,还没有真正初始化这个elementData的大小。
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

//与传来的容量minCapacity(10或size+1)对比,判断elementData实际的容量是否够用
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // 判断是否需要扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

扩容核心方法grow:

    private void grow(int minCapacity) {
        //将扩充前的elementData大小给oldCapacity
        int oldCapacity = elementData.length;
        //newCapacity就是1.5倍的oldCapacity
        int newCapacity = oldCapacity + (oldCapacity >> 1);

        if (newCapacity - minCapacity < 0)
//这句话适应于elementData为空数组时,length=0,则oldCapacity=0,newCapacity=0,minCapacity =10
//所以判断成立,在这里真正的初始化elementData的大小,就是为10
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
//如果newCapacity超过了最大的容量限制,就调用hugeCapacity(),将能给的最大值给newCapacity
            newCapacity = hugeCapacity(minCapacity);
    //新的容量大小已经确定好,就copy数组,进行扩容操作
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
//将能给的最大值给newCapacity
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
//若minCapacity 大于MAX_ARRAY_SIZE,那么返回Integer.MAX_VALUE,反之将MAX_ARRAY_SIZE返回。
//因为maxCapacity比minCapacity大很多,就用minCapacity来判断。
//Integer.MAX_VALUE:2147483647   MAX_ARRAY_SIZE:2147483639  
//也就是说最大也就能给到int型最大值。若超过这个限制,就要溢出。
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

2)void add(int index, E element):在特定位置添加元素,即插入元素

    public void add(int index, E element) {
//判断插入的位置index是否越界
        rangeCheckForAdd(index);
//判断是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
//在插入元素之后,要将index之后的元素都往后移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

3)boolean addAll(Collection c):默认直接在末尾添加集合C中所有元素

    public boolean addAll(Collection c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

4)boolean addAll(int index, Collection c):在特定位置添加集合C中所有元素

    public boolean addAll(int index, Collection c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

小结:调用add方法,先判断是否需要扩容(带索引index时还需要判断索引是否越界),都会进行数组的复制,比修改modCount即+1。
正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。

当调用add方法时,实际上还会进行一系列调用,可能会调用到grow,grow可能会调用hugeCapacity,实际上的函数调用如下:

一、ArrayList源码分析_第2张图片

举例说明一:

List lists = new ArrayList();
  lists.add(8);

初始化lists大小为0,调用的ArrayList()型构造函数,那么在调用lists.add(8)方法时,会经过怎样的步骤呢?下图给出了该程序执行过程和最初与最后的elementData的大小。

一、ArrayList源码分析_第3张图片

可以看到,在add方法之前开始elementData = {};调用add方法时会继续调用,直至grow,最后elementData的大小变为10,之后再返回到add函数,把8放在elementData[0]中。

举例说明二:

List lists = new ArrayList(6);
  lists.add(8);

调用的ArrayList(int)型构造函数,那么elementData被初始化为大小为6的Object数组,在调用add(8)方法时,具体的步骤如下:

一、ArrayList源码分析_第4张图片

可以知道,在调用add方法之前,elementData的大小已经初始化为6,之后再进行传递,不会进行扩容处理。

3.4.2 删

一、ArrayList源码分析_第5张图片

其中private void fastRemove(int index)是private的,是提供给public boolean remove(Object o)用的。

1)public E remove(int index):删除指定位置上的元素

public E remove(int index) {
//检查index的合理性,判断是否越界
        rangeCheck(index);
//修改modeCount 因为结构将发生改变
        modCount++;
//通过索引获取要删除的元素值
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
//复制原数组index之后的元素,来覆盖待删除元素及之后的元素
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
//尾部赋值为null,并修改size,让gc(垃圾回收机制)更快的回收。
        elementData[--size] = null; // clear to let GC do its work
//返回删除的元素。
        return oldValue;
    }

//判断是否越界
private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

//根据下标从数组取值 并强转
    E elementData(int index) {
        return (E) elementData[index];
    }

2)public boolean remove(Object o):删除指定元素

//删除该元素在数组中第一次出现的位置上的数据。 如果有该元素返回true,否则返回false。
//fastRemove(index)方法的内部跟remove(index)的实现几乎一样,知道arrayList可以存储null值即可
    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;
}

3)public boolean removeAll(Collection c):批量删除集合C中的元素

    public boolean removeAll(Collection c) {
        Objects.requireNonNull(c);//判空
        return batchRemove(c, false);//批量删除
    }

    public static  T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

//此方法用于两处,如果complement为false,则用于removeAll()如果为true,则给retainAll()用。
//retainAll()是用来检测两个集合是否有交集的,保留交集中的元素。
    private boolean batchRemove(Collection c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;   //r用来控制循环,w记录剩余元素个数
        boolean modified = false;
        try {
            for (; r < size; r++)
//遍历原数组,判断集合C中是否有数组中的元素
                if (c.contains(elementData[r]) == complement)
//若没有包含,则代表是需要保留的元素,则将其覆盖数组头部元素
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
//出现异常会导致 r !=size , 则将出现异常处后面的数据全部复制到数组里。
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
//删除完毕之后,将数组中w位置之后的元素全部置为null
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

小结:
1)删除操作一定会修改modCount,涉及到数组的复制,相对低效。
2)remove函数删除元素都是将其置为null,为了之后整个数组不被使用时,会被GC更快回收。

3.4.3 去除null,trimToSize

trimToSize方法
(1)修改modCount次数加1
(2)将elementData尾部空余的空间(包括null值)去除,例如:数组长度为10,其中前五个元素有值,其他为空,调用该方法后数组的长度变为5。

 public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

3.4.4 清空,clear

(1)修改modCount次数加1
(2)将数组中每个元素都赋值为null,等待垃圾回收将其回收

    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

3.4.5 改

不会修改modCount,相对增删是高效的操作。

    public E set(int index, E element) {
        rangeCheck(index);// 检验索引是否越界
        E oldValue = elementData(index);//取出旧值 
        elementData[index] = element;//赋新值
        return oldValue;//返回旧值 
    }

3.4.6 查

不会修改modCount,相对增删是高效的操作。

    public E get(int index) {
        rangeCheck(index);// 检验索引是否合法
        return elementData(index);
    }
    E elementData(int index) {
        return (E) elementData[index];//返回的值都经过了向下转型(Object -> E)
    }

3.4.7 包含,contain

//普通的for循环寻找值,根据目标对象是否为null分别循环查找。
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

// 从头部开始查找数组里面是否存在指定元素
//找到第一个和指定元素相等的元素,返回下标
//与此函数对应的lastIndexOf,表示从尾部开始查找。
    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;
    }

3.4.8 迭代器,iterator

interator方法返回的是一个内部类,由于内部类的创建默认含有外部的this指针,所以这个内部类可以调用到外部类的属性。

    public Iterator iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator {
// 下一个要返回元素的索引,默认是0
        int cursor;
//最后一个要返回元素的索引,-1表示不存在  
        int lastRet = -1; 
//记录期望的修改次数,判断集合在迭代中是否修改过结构
//用于保证迭代器在遍历过程中不会有对集合的修改结构操作,迭代器的自身的remove方法除外
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;//游标是否移动至尾部
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();//判断是否修改过结构
            int i = cursor;
            if (i >= size)//判断是否越界
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
//再次判断是否越界,在这里的操作时,是否有异步线程修改了List
                throw new ConcurrentModificationException();
            cursor = i + 1;//游标+1
//返回元素 ,并设置上一次返回的元素的下标
            return (E) elementData[lastRet = i];
        }

//remove 掉 上一次next的元素
        public void remove() {
            if (lastRet < 0)//先判断是否next过
                throw new IllegalStateException();
            checkForComodification();//判断是否修改过结构

            try {
//删除元素 remove方法内会修改 modCount ,所以后面要更新Iterator里的这个标志值
                ArrayList.this.remove(lastRet);
                cursor = lastRet;//要删除的游标
                lastRet = -1; //不能重复删除 所以修改删除的标志位
                expectedModCount = modCount;//更新集合修改的标志
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

//判断是否修改过了集合结构,如果有修改,抛出异常
        final void checkForComodification() {
//实际的修改次数和期望的修改次数不匹配,则抛出并发修改异常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

快速报错机制(fail-fast)的实现原理:为什么会发生并发修改异常?
1)ArrayList从其父类AbstractList继承了一个modCount属性,每当对ArrayList进行修改(add,remove,clear等)时,就会将modCount加1。
2)ArrayList中迭代器的实现类Itr也有一个expectedModCount属性。
3)一旦调用iterator()方法来使用迭代器时,Itr类也就被初始化,expectedModCount就会被赋予一个与modCount相等的值。
4)如果接下来进行遍历操作,则每次调用next()方法获取值时,都会先进行修改检查(checkForComodification),也就是检查modCount和expectedModCount两个值是否相等。
5)如果在遍历过程中进行了对集合的其它修改操作而使得modCount值发生变化,从而造成两者不等,就立即抛出ConcurrentModificationException。

注意:迭代器自身也提供了remove方法,但该方法会同步更新modCount的值赋给expectedModCount,保证该remove方法是安全的,而不希望在迭代时使用ArrayList容器自己提供的add、remove等方法。

如何正确的删除容器中的元素:

    public void setUp(){
        list = new ArrayList();
        list.add("1");        
        list.add("2");
        list.add("3");
    }
//Demo1:使用for循环,删除元素【正确】
    public void testFor(){            
        for(int i=0;i iterator = list.iterator();
        while(iterator.hasNext()){
            String s= iterator.next();
            //删除3
            if("3".equals(s)){
                iterator.remove();//使用迭代器的remove
            }
        }
        System.out.println(list);
    }
//Demo4:使用Iterator,调用集合自身的remove()删除元素【错误】
    public void testIterator2(){
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            String s= iterator.next();
            //删除3
            if("3".equals(s)){
                list.remove(s);//使用list容器的remove
            }
        }
        System.out.println(list);
    }
//Demo5:获得iterator后进行了错误操作【错误】
    public void testIterator3(){
        Iterator iterator = list.iterator();
        //这是错误的操作。获取迭代器后不能再调用容器的修改方法
        list.add("这是错误的行为");
        //这是允许的。
        //iterator.remove(xx);
        
        while(iterator.hasNext()){
            String s= iterator.next();
            //删除3
            if("3".equals(s)){
                iterator.remove();//使用迭代器的remove
            }
        }
        System.out.println(list);
    }

关于java容器中的快速报错机制可参考:
java容器中的快速报错机制

4、ArrayList总结

1)ArrayList本质上是一个可以存放null的elementData数组。
2)ArrayList区别于数组的地方在于能够自动扩展大小,其中关键方法就是gorw()方法。
3)扩容操作会导致数组复制,批量删除会找出两个集合的交集,以及数组复制操作,因此,增、删都相对低效。 而 改、查都是很高效的操作
4)增删改查中, 增删一定会修改modCount, 改查一定不会修改modCount。
5)和Vector区别在于Vector在API上都加了synchronized所以它是线程安全的,以及Vector扩容时,若无指定扩容量则扩容为之前的2倍,而ArrayList是扩容为之前的1.5倍。

5、参考笔记

Java集合源码分析(一)ArrayList
面试必备:ArrayList源码解析(JDK8)

你可能感兴趣的:(一、ArrayList源码分析)