ArrayList部分源码解读

121 阅读4分钟

1、ArrayList简介

ArrayList 的底层是动态数组transient Object[] elementData;因为支持扩容,所以它是动态数组。

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

ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。 

  • 它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。 在我们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1)   
  • ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。   
  • ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,实现这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。   
  • ArrayList 实现了Cloneable 接口,覆盖了函数 clone(),能被克隆。   
  • ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。   

和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。

2、源码解读

2.1 构造函数

//若初始化时不指定长度,默认是10
private static final int DEFAULT_CAPACITY = 10;
//默认的空数组,用于空参构造函数,当添加第一个元素后,数组长度变为10
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//空数组,用于参数为0的构造函数
private static final Object[] EMPTY_ELEMENTDATA = {};

//空参构造函数
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//带参构造函数(参数是数组的初始容量,等于0时和空参构造函数别无二致,小于0时抛出异常)
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
}

2.2 ensureCapacityInternal 及其相关

这两个函数的意义在于,当我们添加某个元素时,有可能会出现

//数组元素个数     区别于数组长度!!
private int size;

//数组缓冲,真正存放数据的地方,当第一个元素加入数组后,数组长度自动由0扩充到10(空参构造的情况下)
transient Object[] elementData;

//该函数用于判断当前数组是否能满足minCapacity容量的储存
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

//当数组是经过空参构造初始化的,则返回10,否则返回minCapacity
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
}
//判断我们目前需要的最小容量是否大于数组缓冲的长度,是的话进行扩容grow。
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
}

2.3 add(E e)

//先确保数组缓冲的容量是否充足,即当我们添加一个元素时,需要 当前元素个数+1 的空间,结合上面的函数
//来看,如果当前元素个数+1不比当前数组缓冲的长度大,则不进行扩容,否则进行扩容
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
}

2.4 add(int index, E element)

public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
//拷贝 索引index开始到结束的元素 到 index+1到末尾的各个位置上
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
//将该索引的元素置为element
        elementData[index] = element;
        size++;
}

//这个函数主要是查看插入索引是否是非法的,即 >size 或 <0 的两种情况
private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

2.5 rangeCheck(int index)

//检查索引是否合法,如果索引>=数组长度,则抛出IndexOutOfBoundsException异常private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

2.6 get(int index)及其相关

E elementData(int index) {
        return (E) elementData[index];
}
//先检查index是否合法,合法则返回相应的元素,不合法抛出异常
public E get(int index) {
        rangeCheck(index);
        return elementData(index);
}

2.7 grow(int minCapacity)及其相关

private void grow(int minCapacity) {
        // overflow-conscious code
        //原始数组长度
        int oldCapacity = elementData.length;
        //新的长度:  原始长度+原始长度右移1位   
        //对于偶数的原始长度来说是1.5倍,但对于奇数来说,应该说是近似1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新的长度比需要的最小长度还小,那我们令新的长度为最小长度
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
//如果新的长度比最大的数组长度MAX_ARRAY_SIZE还大,这时可能是因为需要的最小长度minCapacity为负数了
//即发生溢出情况,抛出内存错误;也有可能是因为需要的最小长度确实>MAX_ARRAY_SIZE
//那么这种情况,新的长度为MAX_ARRAY_SIZE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
 //最后用Arrays.copyOf复制旧数组中的数据
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
}
//Arrays.copyOf方法
public static int[] copyOf(int[] original, int newLength) {
        int[] copy = new int[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
}

3. 其他内容

3.1 System.arraycopy()和Arrays.copyOf()方法

public static int[] copyOf(int[] original, int newLength) {
        int[] copy = new int[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
}

从上面可以看出,Arrays.copyOf()其实还是调用了System.arraycopy,在copyOf中,会新建一个数组并返回
而System.arraycopy需要原数组和目标数组,同时需要他们的开始索引及复制的元素个数