在 Java 编程中,ArrayList是一个常用的集合类,它实现了List接口,底层基于数组实现。与普通定长数组不同,ArrayList能够根据元素的添加情况动态调整数组的大小,这就是其扩容机制。下面我们将深入剖析ArrayList扩容机制的源码。
有的同学可能在别的地方听过一些ArrayList扩容,大部分会说ArrayList底层是数组结构的,默认长度为10,当数组加满后会自动扩容1.5倍。这样的说法不完全对。
正确的步骤应该是如下的:

一、ArrayList的构造方法
1. 无参构造
我们可以自己实际的去探索一下,打开idea,按快捷键ctrl+n,输入ArrayList,选择All Places,点击Java.util那个。

进入之后可以按ctrl+f12,按add,查找add方法,可以发现有很多add方法。

或者按Alt+7将整个大纲罗列出来,这里我们主要看的是空参构造
在这选中elementDate,按住ctrl+B,发现它是一个数组。

我们再看看DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个是什么,就会发现它是一个长度为0的数组,所以空参构造默认初始值为0。

当使用new ArrayList<>()创建对象时,底层会创建一个空数组。相关源码如下:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
这里的DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的Object数组,ArrayList将其赋值给用于存储元素的elementData数组。
这里我将所有代码类全部汇总到一起,可以更好的理解过程。

2. 带初始容量的构造
当使用new ArrayList<>(int size)时,如果传入的size大于 0,会创建一个指定长度的数组;如果size等于 0,会创建一个空数组,与无参构造的情况相同;如果size小于 0,则会抛出IllegalArgumentException异常。源码如下:
public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}
}
其中EMPTY_ELEMENTDATA也是一个空数组,与DEFAULTCAPACITY_EMPTY_ELEMENTDATA有所区别,主要用于标识通过带容量参数且容量为 0 的构造方法创建的情况。
3. 带集合参数的构造
当使用new ArrayList<>(Collection<? extends E> c)时,会先将传入的集合转换为数组,然后判断数组长度:如果长度为 0,按无参构造方式创建空数组;如果不为 0,再判断传入的集合是否是ArrayList类型。如果是,直接将转换后的数组赋值给elementData;如果不是,使用Arrays.copyOf方法进行二次复制,以确保安全性和隔离性。相关源码如下:
public ArrayList(Collection<? extends E> c) {elementData = c.toArray();if ((size = elementData.length) != 0) {if (c.getClass() == ArrayList.class) {this.elementData = elementData;} else {this.elementData = Arrays.copyOf(elementData, size, Object[].class);}} else {this.elementData = EMPTY_ELEMENTDATA;}
}
二、添加元素与扩容触发
当调用add(E e)方法向ArrayList中添加元素时,可能会触发扩容机制。add方法的源码如下:
public boolean add(E e) {ensureCapacityInternal(size + 1); // 确保有足够的容量来存储新元素elementData[size++] = e;return true;
}
其中ensureCapacityInternal(int minCapacity)方法用于确保内部数组有足够的容量来存储新元素。minCapacity表示所需的最小容量,这里是当前元素个数size加 1。
进入ensureCapacityInternal方法:
private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);
}
在这个方法中,如果elementData是默认的空数组(即通过无参构造创建的情况),会将minCapacity设置为默认容量DEFAULT_CAPACITY(值为 10)和minCapacity中的较大值。然后调用ensureExplicitCapacity方法来判断是否需要扩容。
ensureExplicitCapacity方法的源码如下:
private void ensureExplicitCapacity(int minCapacity) {modCount++;if (minCapacity - elementData.length > 0)grow(minCapacity);
}
这里通过比较所需的最小容量minCapacity和当前数组elementData的长度,如果minCapacity大于elementData.length,则说明当前数组容量不足,需要调用grow方法进行扩容。
三、扩容的核心方法grow
grow方法是ArrayList扩容的关键,其源码如下:
private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为原来的1.5倍if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity);
}
在grow方法中,首先获取当前数组的容量oldCapacity,然后通过oldCapacity + (oldCapacity >> 1)计算出一个新的容量newCapacity,这里oldCapacity >> 1表示将oldCapacity右移一位,相当于除以 2,所以新容量是原来的 1.5 倍。
接着会进行两次判断:
- 如果
newCapacity小于minCapacity,说明 1.5 倍扩容后的容量仍不满足需求,此时将newCapacity设置为minCapacity,即按需扩容。 - 如果
newCapacity超过了ArrayList所能支持的最大数组大小MAX_ARRAY_SIZE(在Integer.MAX_VALUE - 8和Integer.MAX_VALUE之间的一个值,具体取决于 JVM 的实现),则调用hugeCapacity方法来确定最终的容量。
最后,通过Arrays.copyOf方法将原数组的元素复制到新的、更大容量的数组中,完成扩容操作。
ArrayList的扩容机制通过巧妙的设计和源码实现,在保证能够动态存储元素的同时,尽量减少不必要的数组复制操作,提高了性能。深入理解其扩容机制,有助于我们在使用ArrayList时更好地进行性能优化和资源管理。