ArrayList的扩容机制

前提:基于1.8的源码进行分析。

在ArrayList中,是使用一个Object[]数组elementData进行数据的存储的,而一个ArrayList的容量就是这个数组elementData的长度length。

观察ArrayList的2个构造函数源码:

折叠复制代码
  1. public ArrayList() {
  2. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  3. }
  4. public ArrayList(int initialCapacity) {
  5. if (initialCapacity > 0) {
  6. this.elementData = new Object[initialCapacity];
  7. } else if (initialCapacity == 0) {
  8. this.elementData = EMPTY_ELEMENTDATA;
  9. } else {
  10. throw new IllegalArgumentException("Illegal Capacity: "+
  11. initialCapacity);
  12. }
  13. }

无参的构造函数,elementData直接为一个默认的特殊的空数组(相当于一个特殊标记,后续扩容代码中发现这个标记会直接扩容为默认的容量10)
指定初始容量的构造函数,elementData会直接实例化一个指定容量的数组;如果初始容量为0,则elementData赋值为一个默认的空数组。

当我们往ArrayList中添加数据时,会通过一个ensureCapacityInternal函数来保证容量的可用性,也就是说ArrayList是先扩容(如果需要的话)后插入数据的

折叠复制代码
  1. /**
  2. * 确保最小容量,如不够则进行扩容
  3. * @param minCapacity
  4. */
  5. private void ensureCapacityInternal(int minCapacity) {
  6. ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
  7. }
  8. /**
  9. * 计算得出需要保证的容量
  10. * 这里正常情况下都是直接返回入参minCapacity的值,只有当elementData的值为上述提到的无参构造函数中设置的特殊数组时,才会返回默认的容量
  11. * @param elementData
  12. * @param minCapacity
  13. * @return
  14. */
  15. private static int calculateCapacity(Object[] elementData, int minCapacity) {
  16. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  17. return Math.max(DEFAULT_CAPACITY, minCapacity);
  18. }
  19. return minCapacity;
  20. }
  21. /**
  22. * 这里会进行一个判断,只有当前elementData数组小于所需的容量时,才会调用grow进行扩容
  23. * @param minCapacity
  24. */
  25. private void ensureExplicitCapacity(int minCapacity) {
  26. modCount++;
  27. // overflow-conscious code
  28. if (minCapacity - elementData.length > 0)
  29. grow(minCapacity);
  30. }

从上方的源码以及注释来看,扩容的代码主要是在grow这个函数中:

折叠复制代码
  1. private void grow(int minCapacity) {
  2. // overflow-conscious code
  3. int oldCapacity = elementData.length;
  4. int newCapacity = oldCapacity + (oldCapacity >> 1);
  5. if (newCapacity - minCapacity < 0)
  6. newCapacity = minCapacity;
  7. if (newCapacity - MAX_ARRAY_SIZE > 0)
  8. newCapacity = hugeCapacity(minCapacity);
  9. // minCapacity is usually close to size, so this is a win:
  10. elementData = Arrays.copyOf(elementData, newCapacity);
  11. }

函数的逻辑其实也不复杂,主要是对elementData的长度进行一个1.5倍的扩容,这里使用了一个位运算来实现,效率比用乘号来的高效。

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

原理是:oldCapacity右移1位,因为是二进制,所以右移1位相当于除以2,再加上原来的oldCapacity,所以得到新的newCapacity相当于是oldCapacity的1.5倍(当然这里面有一个向下取整的精度问题,忽略不计)

grow函数在通过位运算计算出新数组的长度之后,因为oldCapacity可能为0,以及1.5倍向下取整的问题,得到的新容量newCapacity可能还是会比所需容量小,这种情况下新数组容量会直接等于所需容量。额外在考虑一下MAX_ARRAY_SIZE的情况,最终可以得到一个扩容后的新容量。

获取到扩容之后的新容量值,接下来就是直接创建一个新数组并进行一个数据的拷贝,ArrayList的扩容就结束了。


觉得内容还不错?打赏个钢镚鼓励鼓励!!👍