在 Java 集合框架中,ArrayList 是我们日常开发中最常用的动态数组实现。它底层基于数组实现,却能在元素数量超过容量时自动扩容,让我们无需手动管理数组大小。今天我们就来拆解 ArrayList 的动态扩容机制,帮你彻底搞懂它的工作原理与性能优化点。
一、核心概念:容量 vs 大小
在聊扩容之前,我们先分清两个容易混淆的概念:
- 容量(Capacity):底层数组 elementData 的长度,代表 ArrayList 最多能容纳多少元素。
- 大小(Size):当前实际存储的元素个数,通过 size() 方法获取。
举个例子:一个刚创建的 ArrayList,容量可能是 10,但如果还没添加任何元素,它的大小就是 0。
二、扩容触发时机
扩容并不是随时发生的,只有在添加元素时才会触发:
当我们调用 add(E e) 或 addAll() 等方法时,ArrayList 会先检查:
如果 size + 1 > 当前容量,就说明数组放不下新元素了,必须触发扩容。
简单来说:装不下了,才扩容。
三、JDK 8 扩容完整流程
我们以 JDK 8 为例,一步步拆解扩容的核心逻辑。
1. 入口:add() 方法
add() 是扩容的起点,核心代码如下:
public boolean add(E e) {
// 第一步:确保容量足够
ensureCapacityInternal(size + 1);
// 第二步:赋值并自增 size
elementData[size++] = e;
return true;
}
可以看到,添加元素分为两步:
2. 容量检查:ensureCapacityInternal
这个方法会处理初始容量的特殊情况,并调用真正的扩容判断逻辑:
private void ensureCapacityInternal(int minCapacity) {
// 如果是刚创建的空数组,首次扩容直接用默认容量 10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 记录集合修改次数(用于迭代器快速失败)
// 如果需要的最小容量 > 当前数组长度,才执行扩容
if (minCapacity – elementData.length > 0)
grow(minCapacity);
}
3. 核心扩容:grow() 方法
grow() 是扩容的核心,负责计算新容量并创建新数组:
private void grow(int minCapacity) {
// 1. 获取旧容量
int oldCapacity = elementData.length;
// 2. 新容量 = 旧容量 + 旧容量 / 2(即扩容 1.5 倍)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 3. 特殊情况:如果 1.5 倍扩容后仍不够,直接用需要的最小容量
if (newCapacity – minCapacity < 0)
newCapacity = minCapacity;
// 4. 超大容量处理:防止超过 Integer.MAX_VALUE
if (newCapacity – MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 5. 复制原数组到新数组(性能开销最大的一步)
elementData = Arrays.copyOf(elementData, newCapacity);
}
4. 关键细节总结
- 默认初始容量:new ArrayList<>() 创建的空数组,首次添加元素时容量会直接扩容到 10。
- 默认扩容比例:每次扩容为原容量的 1.5 倍(oldCapacity + oldCapacity/2)。
- 兜底策略:如果 1.5 倍扩容后仍无法满足需求,会直接使用需要的最小容量。
- 容量上限:数组最大长度为 Integer.MAX_VALUE – 8,极端情况会使用 Integer.MAX_VALUE。
四、代码示例:直观感受扩容变化
我们通过反射来查看 ArrayList 底层数组的容量变化:
import java.util.ArrayList;
import java.lang.reflect.Field;
public class ArrayListExpandDemo {
public static void main(String[] args) throws Exception {
ArrayList<String> list = new ArrayList<>();
Field field = ArrayList.class.getDeclaredField("elementData");
field.setAccessible(true);
// 初始状态:空数组,容量 0
System.out.println("初始容量:" + ((Object[]) field.get(list)).length); // 0
// 添加第 1 个元素:扩容到 10
list.add("a");
System.out.println("添加1个元素后容量:" + ((Object[]) field.get(list)).length); // 10
// 添加到第 10 个元素:容量保持 10
for (int i = 1; i < 10; i++) list.add("a" + i);
System.out.println("添加10个元素后容量:" + ((Object[]) field.get(list)).length); // 10
// 添加第 11 个元素:扩容到 15(10 + 10/2)
list.add("b");
System.out.println("添加11个元素后容量:" + ((Object[]) field.get(list)).length); // 15
}
}
运行结果:
初始容量:0
添加1个元素后容量:10
添加10个元素后容量:10
添加11个元素后容量:15
五、性能影响与优化建议
1. 扩容的性能开销
扩容的核心是 Arrays.copyOf(),它需要遍历原数组并将所有元素复制到新数组中。元素越多,扩容耗时越长,频繁扩容会严重影响性能。
2. 优化方案
- 提前指定初始容量:如果已知元素数量(比如要存 1000 个元素),创建时直接 new ArrayList<>(1000),避免多次扩容。
- 手动触发扩容:不确定数量时,可调用 ensureCapacity(int minCapacity) 手动扩容,减少后续扩容次数。
// 示例:提前扩容到 1000
ArrayList<String> list = new ArrayList<>();
list.ensureCapacity(1000);




