在 Java 中,ArrayList 的动态扩容机制依赖于其内部的 grow() 方法。结合具体添加元素的过程,我们可以清晰看到扩容是如何一步步发生的。
🌰 示例:逐步追踪 grow() 行为
1. 添加第 1 个元素
- 初始:
elementData = [](空数组),size = 0 - 调用
add(e)→ensureCapacityInternal(1) - 因是默认构造,
minCapacity = max(10, 1) = 10 - 进入
grow(10):
oldCapacity = 0newCapacity = 0 + (0 >> 1) = 0- 第一个 if 成立:
0 < 10→newCapacity = 10 - 第二个 if 不成立:
10 < MAX_ARRAY_SIZE(通常为Integer.MAX_VALUE - 8) - 执行
Arrays.copyOf(elementData, 10)
- 结果:容量变为 10,
size = 1
2. 添加第 11 个元素
- 此时:
elementData.length = 10,size = 10 minCapacity = 11- 进入
grow(11):
oldCapacity = 10newCapacity = 10 + (10 >> 1) = 15- 第一个 if 不成立:
15 ≥ 11 - 第二个 if 不成立:
15远小于最大数组大小 - 执行
Arrays.copyOf(..., 15)
- 结果:容量变为 15,
size = 11
3. 后续扩容
- 第 16 个元素 → 扩容至
15 + 7 = 22 - 第 23 个元素 → 扩容至
22 + 11 = 33 - ……依此类推,始终按 1.5 倍增长,直到满足需求或触及上限。
⚠️ 只有当
newCapacity > MAX_ARRAY_SIZE时,才会调用hugeCapacity(),处理超大数组(极罕见场景)。
🔍 容易混淆的“长度”概念:length、length()、size()
这是 Java 初学者常踩的坑,务必区分清楚:
| 类型 | 获取方式 | 说明 |
| 数组(Array) | arr.length |
属性,不是方法。例如:int[] a = new int[5]; a.length → 5 |
| 字符串(String) | str.length() |
方法。例如:"hello".length() → 5 |
| 集合(Collection / List / Set 等) | list.size() |
方法。例如:new ArrayList<>().size() → 0 |
✅ 记忆口诀:
- 数组用
.length(无括号) - 字符串用
.length()(有括号) - 集合用
.size()(语义更清晰)
❌ 错误示例:
List<String> list = new ArrayList<>(); int len = list.length; // 编译错误! String s = "abc"; int l = s.length; // 编译错误!
总结
ArrayList的grow()方法通过 1.5 倍扩容 + 最小容量兜底,平衡性能与内存;- 首次添加即扩容到 10,避免小步快跑;
- 扩容本质是 数组复制,应尽量通过预设容量避免;
- 牢记
length、length()、size()的使用场景,写出更规范的代码。
理解这些细节,不仅能避免性能陷阱,也能提升代码的健壮性与可读性。