Java之ArrayList的扩容机制源码分析

前言

        Java中,ArrayList是一个使用非常频繁的集合类型,它的底层是Object数组,所以它拥有数组所拥有的特性,比如支持随机访问,所以查询效率高,但内部插入数据,删除数据需要移动大量元素,所以效率相对较LinkedList差一些。

调用ArrayList的无参构造方法:

transient Object[] elementData;

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

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

        构造方法中,它将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData,这个DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的Object数组,而elementData就是ArrayList实际存储数据的容器。

        ArrayList在调用无参构造方法时创建的是一个长度为0的空数组,当调用add()方法添加元素时,ArrayList才会触发扩容机制:

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

        add()方法的第一行即是执行扩容流程:

private void ensureCapacityInternal(int minCapacity) {
    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就是一个空数组,条件成立,它会从DEFAULT_CAPACITY和minCapacity中选择一个最大值返回,其中DEFAULT_CAPACITY表示默认的初始容量,它的值默认设置为10:

private static final int DEFAULT_CAPACITY = 10;

minCapacity是add()方法传递过来的,值为size + 1:(add()首行方法一直进入到计算扩容量方法内);

ensureCapacityInternal(size + 1);  // Increments modCount!!

所以calculateCapacity()方法将返回10,之后调用ensureExplicitCapacity()方法:

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

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

        首先modCount++,这是用来记录数组被修改次数的变量,此时minCapacity的值为10,elementData.length的值为0,条件成立,执行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);
}

其中:

int newCapacity = oldCapacity + (oldCapacity >> 1);

        先将旧容量右移1位,再加上旧容量就得到了新容量,正数右移1位相当于除以2,在该基础上加旧容量,则等价于新容量 = 旧容量 * 1.5,所以才有ArrayList每次扩容为旧容量的1.5倍的说法,最后调用Arrays.copyOf()方法进行拷贝,并将elementData指向新数组,而旧数组因为没有引用指向它,很快就会被垃圾收集器回收掉。

        第二次调用add()方法时,程序依然要走到扩容方法:    

    

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

        此时的elementData已经不是空数组了,所以直接返回当前size + 1,即:2,接着调用:

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

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

        当放入元素大于minCapacity,即放入个数大于10时。ArrayList应该会触发第二次扩容:

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

        minCapacity的值为11,紧接着调用它:

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

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

此时minCapacity的值大于elementData的长度,条件成立,触发扩容机制:

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);
}

将原容量10右移一位得到5,再加上原容量10得到15,所以新数组的容量为15,最后对数组进行拷贝扩容就完成了。

当ArrayList进行第三次扩容后容量会是多少呢?我们知道,新容量一定是旧容量的1.5倍,而15 * 1.5 = 22.5,那么新容量到底是22还是23呢?所以,如果你只知道新容量是旧容量的1.5倍,这个问题你就无法知道。事实上,ArrayList底层是通过移位操作计算得到的新容量。所以新容量应该等于15 >> 1 + 15 = 22,由此可得,ArrayList经过第三次扩容后容量为22。然而在addAll()方法中,扩容机制会有一定的变化,比如:

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.addAll(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11));
}
public boolean addAll(Collection<? extends E> 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;
}

它将调用ensureCapacityInternal()方法进行扩容,并传入 size + numNew = 12:

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

因为初始elementData是一个空数组,符合条件,所以它将返回DEFAULT_CAPACITY和minCapacity中较大的那个,结果是minCapacity较大,所以返回12,这就导致addAll()方法执行结果后ArrayList的容量为12。

addAll()方法总是选择扩容一次后的容量与旧容量加上添加的元素个数的容量中取一个最大值作为新的容量,比如:当前ArrayList中有10个元素,而addAll()方法需要添加6个元素,当ArrayList触发扩容后的新容量应该为15,而旧容量加上需要添加的元素容量为16,从中取一个较大值为16,所以新容量应该为16。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值