ArrayList扩容机制

201 阅读3分钟

本篇文章以无参构造方法构建ArrayList的过程说明扩容机制,即

ArrayList<Integer> list = new ArrayList<>();
list.add(1);

看一下用到的成员变量:

// 默认容量,DEFAULTCAPACITY_EMPTY_ELEMENTDATA用
private static final int DEFAULT_CAPACITY = 10;
// 无参构造器初始化用
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList中拥有元素的数目
private int size;

初始化

从构造器说起:

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

在使用无参构造器初始化ArrayList的时候,ArrayList存储数据的内部数组被初始化名为DEFAULTCAPACITY_EMPTY_ELEMENTDATA的数组,这个数组定义如下:

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

这是个容量为DEFAULT_CAPACITY的数组,DEFAULT_CAPACITY的默认值为10,在这里看不出这个数组初始容量为10,在下面的流程中就会看见,为什么这个数组初始容量为10.

添加元素与扩容

list.add(1)所调用的add方法的源码:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

这个方法将元素添加到ArrayList的末尾位置。

进入add方法,ensureCapacityInternal就是来确保当前ArrayList拥有足够的空间,其源码如下:

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

其参数是minCapacity,表示需求的最小的容量,即size+1,其中size表示当前ArrayList中存储的元素的个数,添加一个元素,即size+1可以保证最小要求。看ensureCapacityInternal内部,是ensureExplicitCapacity函数,该函数的作用是来确定是否需要扩容。这个函数的实参是calculateCapacity(elementData, minCapacity),也是一个函数。先看这个实参:

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

该函数首先判断elementData是否和DEFAULTCAPACITY_EMPTY_ELEMENTDATA相等,这就是在初始化时使用的数组,如果相等,那就返回DEFAULT_CAPACITYminCapacity(即size+1)中的较大者。这也就是为什么DEFAULTCAPACITY_EMPTY_ELEMENTDATA初始化容量为10. 如果ArrayList的size为0,即拥有0个元素,添加一个元素后,size+1=1,此时,10 > 1,返回的就是10。即ArrayList的初始容量为10. 返回容量后,该参数传入ensureExplicitCapacity,看该函数的源码:

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

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

这个函数内部,比较了计算得到的容量(即我们需要的容量)和elementData这个ArrayList内部数组容量大小,如果前者大于后者,说明容量不够,需要进行扩容,这是调用grow方法。即扩容的核心函数:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

先使用oldCapacity保存旧容量,newCapacity保存新容量,oldCapacity + (oldCapacity >> 1)即原来容量的1.5倍,>>符号二进制右移1位,表示除以2。这种写法运算速度快,也有防止越界的功能。具体可以进行进一步的查询。接下来会有两个判断,第一个判断新容量是否比旧容量还小,这个就新容量就赋值为旧容量。第二个判断是看新容量是否超过了最大数组容量,如果超过了,调用hugeCapacity函数,这个函数如下:

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

这个函数要么返回最大值(超过),要么返回计算得到的容量(没超过)。在确定新容量之后,就使用copyOf方法,将旧 数组内容复制到具有新容量的数组中,完成扩容。

以上便是使用无参构造器创建ArrayList,然后进行扩容的过程。其它方式,如指定初始大小,或者使用集合作为初始化参数,可自行分析下。