【源码分析】ArrayList的扩充流程

ArrayList的扩充一般都是通过往里面添加元素触发的。也就是add()方法触发的。所以我们首先定位到add()方法。

一般来说,都是通过 add 方法触发扩容机制,我们拿最简单的尾部追加的 add() 方法举例

public boolean add(E e) {
    // 确认 list 容量,尝试容量加 1,看看有无必要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 赋值
    elementData[size++] = e;
    return true;
}
复制代码

添加数据之前先确认容量是否充足,有无必要扩容

/**
 * 得到最小扩容量
 */
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
复制代码

这里面首先对参数中的calculateCapacity()方法进行追踪。

/**
 * 计算最小扩容量(被调用)
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
     // 如果元素数组为默认的空
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 获取“默认的容量”和“传入参数 minCapacity ”两者之间的最大值
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
复制代码

当数组中的元素是默认的空数组,数组没有经过扩容的时候,返回的是默认的10这个最小扩容量,否则返回的仍然是minCapacity.也就是说,这个方法主要是用来判断是否是第一次扩容,如果是第一次扩容就直接设定最小空间是10,否则最小空间仍然是当前空间+1.

在确认是否是第一次扩容,拿到最小的容量之后,我们再看看ensureExplicitCapacity() 继续去看它

/**
 * 判断是否需要扩容
 */
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    // 如果最小容量比数组的长度还大
    if (minCapacity - elementData.length > 0)
        // 就调用grow方法进行扩容
        grow(minCapacity);
}
复制代码

该方法的核心是用需要的最小容量来减去目前数据的长度,如果需要的容量大于目前的长度,那么就去扩容,否则就不要扩容 这里注意三种情况:

  • 在初始化后添加第一个元素的时候,数组还是个空数组,这时候前面已经设定了需要的容量是10,而数组长度是0,那么就直接进行扩容阶段。
  • 当添加2 到10 个元素的时候,那么由于需要的容量不在是默认的10,而是size + 1,那么而数组经过第一次扩容已经变为了10,那么条件不成立,就不会进行扩容,
  • 当添加第11个元素的时候,需要的最小容量终于大于了数组的长度,那么就会进行扩容。

接下来就是真正的扩容阶段:

/**
 * 要分配的最大数组大小
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 * ArrayList 扩容的核心方法
 */
private void grow(int minCapacity) {
    // 将当前元素数组长度定义为 oldCapacity 旧容量
    int oldCapacity = elementData.length;
    // 新容量更新为旧容量的1.5倍
    // oldCapacity >> 1 为按位右移一位,相当于 oldCapacity 除以2的1次幂
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 然后检查新容量是否大于最小需要容量,若还小,就把最小需要容量当作数组的新容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 再检查新容量是否超出了ArrayList 所定义的最大容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // 若超出,则调用hugeCapacity()
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}
复制代码

扩容的核心是将容量扩大到原来的1.5倍。也就是代码中的int newCapacity = oldCapacity + (oldCapacity >> 1); 这里计算出容量过后,如果扩充后的容量比需要的容量还小,那么就直接让容量变为需要的容量,也就是在第一次扩容的时候,就不是将容量扩充为1.5倍了,而是直接变为了默认容量10。

【提醒:该流程走的是空参构造方法,对于不是空参的构造方法,不会设置数据数组为“{}”这个对象,那么在判定需要的最小容量的时候就不会指定为默认的10,所以,只有空参构造的时候,第一次初始化会直接扩充到10】

/**
 * 空参构造
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
复制代码

对于不是没有利用空参创建的实例,第一次扩容就会扩容到1.5倍,也就是0的1.5倍,还是0,其值小于了需要的最小容量minCapacity = 0 + 1;所以首次扩容到1,后面正常进行扩容即可。

总结: 扩容可分为两种情况:

  第一种情况,当ArrayList的容量为0时,此时添加元素的话,需要扩容,三种构造方法创建的ArrayList在扩容时略有不同:

    1.无参构造,创建ArrayList后容量为0,添加第一个元素后,容量变为10,在1 - 10 内不需要再扩容,此后若需要扩容,则正常扩容。

    2.传容量构造,当参数为0时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。

    3.传列表构造,当列表为空时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。

  第二种情况,当ArrayList的容量大于0,并且ArrayList是满的时,此时添加元素的话,进行正常扩容,每次扩容到原来的1.5倍。

猜你喜欢

转载自juejin.im/post/7036657156925423646