一、触发条件
当向 ArrayList
添加元素时(如 add()
、addAll()
方法),若当前元素数量(size
)已达到数组容量(elementData.length
),则会触发扩容。
二、扩容流程
-
计算最小容量
所需最小容量为当前元素数量size + 1
(单个添加)或size + numNew
(批量添加)。 -
检查当前容量
若当前数组容量不足,调用grow(minCapacity)
方法进行扩容。 -
计算新容量
- 默认策略:新容量为原容量的 1.5 倍(即
oldCapacity + (oldCapacity >> 1)
)。 - 特殊情况:若新容量仍不满足最小需求,则直接使用最小容量。
- 默认策略:新容量为原容量的 1.5 倍(即
-
处理最大容量限制
若新容量超过MAX_ARRAY_SIZE
(Integer.MAX_VALUE - 8
),则扩容至Integer.MAX_VALUE
。 -
数组复制
使用Arrays.copyOf()
创建新数组,并将原数据复制到新数组中。
三、源码关键方法
// JDK 17 源码核心逻辑
private Object[] grow(int minCapacity) {int oldCapacity = elementData.length;if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, // 最小增量oldCapacity >> 1 // 默认增量(原容量的一半));return elementData = Arrays.copyOf(elementData, newCapacity);} else {// 初始空数组首次扩容return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];}
}
四、扩容策略示例
原容量 | 操作 | 新容量 | 计算逻辑 |
---|---|---|---|
0(初始) | 首次添加元素 | 10 | 默认初始容量 |
10 | 添加第 11 个元素 | 15 | 10 + (10 >> 1) = 15 |
15 | 添加第 16 个元素 | 22 | 15 + 7 = 22(15 >> 1 = 7) |
10 | 批量添加 20 个元素 | 30 | 原容量 10+10=20 < 30 → 直接扩容至 30 |
五、性能优化建议
-
预分配容量
构造时指定初始容量,避免多次扩容:// 已知需要存储1000个元素 List<Integer> list = new ArrayList<>(1000);
-
批量操作优化
使用addAll()
替代循环添加,减少扩容次数:// 高效批量添加 list.addAll(Arrays.asList(1, 2, 3, 4, 5));
-
内存回收
对不再扩容的列表调用trimToSize()
,释放多余空间:list.trimToSize(); // 将数组容量调整为当前元素数量
六、特殊场景处理
-
空列表首次扩容
使用无参构造器时,初始数组为空。首次添加元素时,容量直接设为10
。 -
超大容量处理
当尝试扩容超过Integer.MAX_VALUE - 8
时,可能抛出OutOfMemoryError
。
七、与 Vector 的对比
特性 | ArrayList | Vector |
---|---|---|
扩容倍数 | 1.5 倍 | 2 倍 |
线程安全 | 非线程安全 | 同步方法(线程安全) |
性能 | 更高 | 较低(锁开销) |
总结
ArrayList
的扩容机制通过 1.5 倍动态增长 平衡了内存利用与性能开销。理解其内部实现有助于在开发中:
- 避免频繁扩容带来的性能损耗
- 合理预分配容量优化内存使用
- 选择适合场景的列表实现类(如高并发时使用
CopyOnWriteArrayList
)