ArrayList扩容算法及底层实现

一.先看结论

  1. 利用无参构造创建ArrayList则初始容量为0,第一次扩容:容量变为10;第二次及往后扩容:容量变为原来的3/2。
  • 如: List<Integer> list = new ArrayList<>();,初始容量为0,当第一次添加数据时扩容为10,第二次扩容为10+(10/2)=15;第三次为15+(15/2)=22......以此类推。
  1. 利用有参构造指定容量,则初始容量就是指定的容量,每次扩容时容量变为原来的3/2.
  • 如:List<Integer> list = new ArrayList<>(8);,初始容量为8,第二次扩容为8+(8/2)=12;第三次扩容为:12+(12/2)=18.....以此类推。

二. 源码分析

  1. 首先看无参构造
public class ArrayListTest {
    public static void main(String[] args) {
    //这里利用无参构造创建了一个list集合
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 12; i++) {
            list.add(i);
        }
    }
}
复制代码
  • dubug调试

1.1 创建list image.png

1.2 第一次添加数据,因为初始容量为0,所以第一次添加数据就要扩容

image.png

1.3 当list存满10个元素时,需要第二次扩容

image.png

image.png

  • 源码分析

不看源码的猜测都是耍流氓,因此咱们看看源码具体是如何实现扩容的。

  1. 当调用无参构造时,给elementData赋值一个空数组

image.png

image.png 2. 可以看到每次调用add方法都会调用ensureCapcityInternal方法确保当前容量是否足以增加一个元素

image.png 3. 进入ensureCapcityInternal看其是如何确保容量充足的

image.png 这里可以发现ensureCapcityInternal不过是加了个if判断,又调用了另一个方法,而这个if判断的看起来仅仅是让minCapacity在DEFAULT_CAPACITYminCapacity之间取一个较大值。这里并没有发现扩容操作,可见奥秘应该在ensureExplicitCapacity方法中。其实这步if判断的含义是"如果elementData是个空数组,则在DEFAULT_CAPACITY,和minCapacity中取个较大值"。因为我们用的是空参构造,在空参构造中elementData赋值了一个默认的空数组,而DEFAULT_CAPACITY是个静态常量=10,第一次添加元素,所需的最小容量(minCapacity)就是1,1<10,较大值当然是DEFAULT_CAPACITY了。这里很重要,记得此时经过if判断后minCapacity=10。

  1. 接下来我们看看ensureExplicitCapacity方法有什么奥秘

image.png 这里我们可以发现一个新方法grow(),从语义上来说很可能它就是真正执行扩容的方法,而这里if的含义是"如果所需的最小容量>当前数组的长度则进行grow()",大白话就是说我需要的最小容量当前数组已经满足不了了,需要进行grow()操作。

  1. grow是真正扩容的地方

image.png oldCapacity用于保存旧数组长度,newCapacity=oldCapacity + (oldCapacity右移一位)其实就是新容量=旧容量+旧容量/2(扩容为原来的1.5倍);(位运算的效率比乘除高),第一个if的作用是防止第一次扩容失败,因为第一次扩容时由于elementData是个空数组,自然长度就是0,那么oldCapacity得到的也是0,则第一次的newCapacity也是0,这个if就是防止第一次扩容后newCapacity的值还是0.还记得前面minCapacity的值在ensureCapcityInternal方法中已经变成10了吗?这就是为什么第一次扩容,容量为10的原因。而后续扩容时这个if条件将不再满足,newCapacit会等于oldCapacity的1.5倍。至此结论一论证完毕。

明白了无参构造是如何扩容的,其实有参的更简单,建议大家自己可以自己追一追源码。下次续上

猜你喜欢

转载自juejin.im/post/7106168465211260942