ArrayList 是 Java 中常用的动态数组实现,其自动扩容机制是保证它能够灵活存储元素的核心特性。以下是其扩容机制与原理的详细说明:
1. 核心存储结构
ArrayList 内部通过一个 Object 类型的数组(elementData) 存储元素,该数组的长度决定了当前集合的容量(capacity)。
- 当创建 ArrayList 时(如
new ArrayList()
),默认初始容量为 10(JDK 8 及以上)。 - 也可以通过构造方法指定初始容量(如
new ArrayList(20)
),避免频繁扩容。
2. 触发扩容的时机
当调用 add()
方法添加元素时,会先检查当前数组是否还有剩余空间:
- 如果数组已满(元素数量
size == elementData.length
),则触发扩容。 - 扩容的本质是创建一个 更大的新数组,并将原数组的元素复制到新数组中,最后用新数组替代原数组。
3. 扩容的具体步骤
步骤 1:计算新容量
默认扩容策略是 将原容量增加一半(即新容量 = 原容量 + 原容量 / 2 = 原容量 * 1.5 倍)。
例如:原容量为 10 时,扩容后为 15;原容量为 15 时,扩容后为 22(整数除法,15 + 7 = 22)。
步骤 2:处理特殊情况
- 如果计算出的新容量仍小于实际需要的最小容量(如添加大量元素时),则直接使用最小容量作为新容量。
- 如果新容量超过 ArrayList 允许的最大容量(
Integer.MAX_VALUE - 8
),则使用Integer.MAX_VALUE
作为新容量(避免溢出)。
步骤 3:数组复制
通过 Arrays.copyOf()
方法创建新数组,并将原数组元素复制到新数组中,然后更新 elementData
引用指向新数组。
4. 关键源码解析(JDK 8)
以下是 ArrayList
中与扩容相关的核心代码:
// 添加元素时检查是否需要扩容
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保容量至少为 size + 1
elementData[size++] = e;
return true;
}
// 计算最小需要的容量
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 计算需要的容量(如果是初始空数组,使用默认容量10)
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 检查是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 记录集合修改次数(用于快速失败机制)
// 如果需要的容量超过当前数组长度,则扩容
if (minCapacity - elementData.length > 0) {
grow(minCapacity);
}
}
// 核心扩容方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容1.5倍(右移1位等价于除以2)
// 如果新容量仍不够,直接使用需要的最小容量
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
// 处理超过最大容量的情况
if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}
// 复制原数组到新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 处理超大容量
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
5. 扩容的性能影响
- 时间成本:扩容时需要复制数组,时间复杂度为 O(n)(n 为原数组长度),频繁扩容会降低性能。
- 空间成本:扩容后的数组可能存在冗余空间(如 10 → 15,若只需存储 11 个元素,则浪费 4 个空间)。
优化建议:如果能预估元素数量,创建 ArrayList 时指定初始容量(如 new ArrayList(1000)
),可减少扩容次数,提高效率。
总结
ArrayList 的自动扩容机制通过 1.5 倍扩容策略 和 数组复制 实现,既保证了动态存储的灵活性,也在时间和空间成本之间做了平衡。理解这一机制有助于更好地使用 ArrayList,避免不必要的性能损耗。