前言

首先推荐一个特别好的git仓库:https://github.com/wupeixuan/JDKSourceCode1.8
有比较详细的中文注释,大家可以git下来或者fork到自己的仓库仔细研读。

正文

在解读List源码之前,需要对数据结构中的数组、链表有一定的认识。
如有需要,可以参考本人的博客:
数组

ArrayList简介

  • 概述:List接口可调整大小的数组实现。实现所有可选的List操作,并允许所有元素,包括null,元素可重复
  • 时间复杂度:方法size、isEmpty、get、set是常数时间的。添加删除的时间复杂度为O(N)。
  • 容量:每个ArrayList都有容量,容量大小至少为List元素的长度,默认初始化为10。也可以通过带初始容量的构造器初始化这个容量。容量可以自动增长。
  • ArrayList是线程不安全的。如果需要应用到多线程中,需要在外部做同步。
  • 快速失败: modeCount,定义在AbstractList中:protected transient int modCount = 0;已从结构上修改此列表的次数。从结构上修改是指更改列表的大小,或者打乱列表,从而使正在进行的迭代产生错误的结果。在并发环境下,每次迭代时会比较modeCount的值是否与当前相等,如果不相等说明有别的线程修改了数组,则会抛出异常,这就是快速失败。

ArrayList属性

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, 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;

    /**
     * 元素数量
     *
     * @serial
     */
    private int size;
    // 省略方法
}

ArrayList构造方法

无参构造方法

  • 如果不传入参数,则使用默认无参构建方法创建ArrayList对象
  • 此时elementData 的size为0,当第一次add()时会变为默认的长度:10
public ArrayList() {
    // 直接将空数组赋给elementData
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

带有容量initialCapacity的构造方法

  • 传入参数代表ArrayList的初始长度,如果大于0,new一个指定大小的Object数组赋值给elementData,如果等于0,则默认将空数组EMPTY_ELEMENTDATA赋值给elementData,小于0则抛出IllegalArgumentException,参数不合法。
public ArrayList(int initialCapacity) {
    // 如果初始化时ArrayList大小大于0
    if (initialCapacity > 0) {
        // new一个该大小的object数组赋给elementData
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {// 如果大小为0
        // 将空数组赋给elementData
        this.elementData = EMPTY_ELEMENTDATA;
    } else {// 小于0
        // 则抛出IllegalArgumentException异常
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
}

带参数Collection的构造方法

  • 将集合对象转换为数组,然后将数组的地址的赋给elementData。
  • 更新size的值,同时判断size的大小,如果是size等于0,直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData。
  • 如果size的值大于0,则执行Arrays.copy方法,把collection对象的内容(可以理解为深拷贝)copy到elementData中。
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toarray可能(错误地)不返回对象[](见JAVA BUG编号6260652)
        if (elementData.getClass() != Object[].class) {
            elementData = Arrays.copyOf(elementData, size, Object[].class);
        }
    } else {
        // 使用空数组
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

add方法

add(E e),添加到ArrayList末尾

  • 首先需要判断数组是否能够存储下这个元素
  • 如果是第一次添加,则会将数组长度初始化为默认长度:10
  • 如果存储得下,直接将元素e赋值给elementData的size+1的位置
  • 如果存储不下,则调用grow方法,将数组长度扩容为原来的1.5倍

实现代码:

方法入口:
public boolean add(E e) {
    // 根据情况扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将e赋值给elementData的size+1的位置
    elementData[size++] = e;
    return true;
}

// 扩容
private void grow(int minCapacity) {
    // 获取到ArrayList中elementData数组的内存空间长度
    int oldCapacity = elementData.length;
    // 扩容至原来的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 再判断一下新数组的容量够不够,够了就直接使用这个长度创建新数组,
    // 不够就将数组长度设置为需要的长度
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //若预设值大于默认的最大值检查是否溢出
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间
    // 并将elementData的数据复制到新的内存空间
    elementData = Arrays.copyOf(elementData, newCapacity);
}

add(int index, E element) 添加元素到指定位置

  • 先判断指定的位置是否数组越界
  • 根据情况扩容
  • 将elementData从index位置后的数组,复制到elementData的index+1开始的连续空间
  • 在elementData的index位置赋值element

代码:

public void add(int index, E element) {
    // 判断index是否越界
    rangeCheckForAdd(index);
    // 扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
    //src:源数组; srcPos:源数组要复制的起始位置; dest:目的数组; destPos:目的数组放置的起始位置; length:复制的长度
    // 将elementData从index位置开始,复制到elementData的index+1开始的连续空间
    System.arraycopy(elementData, index, elementData, index + 1,
            size - index);
    // 在elementData的index位置赋值element
    elementData[index] = element;
    // ArrayList的大小加一
    size++;
}

remove(int index)删除指定位置的元素

  • 判断index是否数组越界
  • 将elementData数组index+1后的数组拷贝到elementData从index开始的空间
  • 使size-1 ,设置elementData的size位置为空,让GC来清理内存空间
  • 返回之前的值

代码如下:

public E remove(int index) {
    // 判断是否越界
    rangeCheck(index);

    modCount++;
    // 读取旧值
    E oldValue = elementData(index);
    // 获取index位置开始到最后一个位置的个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 将elementData数组index+1位置开始拷贝到elementData从index开始的空间
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    // 使size-1 ,设置elementData的size位置为空,让GC来清理内存空间
    elementData[--size] = null; //便于垃圾回收器回收

    return oldValue;
}

省略get、set。