能,但只对新增有效且需在大量add前调用;它不减少remove开销、不回收空间,仅避免add触发的多次扩容拷贝。

ensureCapacity() 真的能减少重分配吗?
能,但只对「新增」有效,且必须在大量 add() 操作前调用;它对 remove() 完全无影响,也不会回收已分配空间。ArrayList 内部数组扩容只发生在 add() 触发容量不足时,而 ensureCapacity() 的作用就是提前把底层数组(elementData)扩到指定大小,避免后续多次 resize —— 每次 resize 都涉及新数组分配 + 元素拷贝,是 O(n) 开销。
常见误判:以为调用一次 ensureCapacity(100) 后再删掉 90 个元素,内存就会自动缩容 —— 不会。ArrayList 从不自动缩小数组,trimToSize() 才干这事,而且它也不属于“减少重分配”,而是释放冗余内存。
什么时候调用 ensureCapacity() 才真正省开销?
典型场景是「可预估最终规模的批量构建」,比如从数据库查出 5000 条记录逐个 add,或解析一个已知长度的 JSON 数组。此时在循环前调用 ensureCapacity() 可避免多次扩容(默认初始容量 10,扩容策略是 1.5 倍,5000 个元素至少触发约 12 次数组拷贝)。
- ✅ 推荐:已知最终 size(如
list.size() == expectedSize成立前),且主要操作是add() - ❌ 无效:边 add 边 remove、或插入位置随机(
add(int index, E)可能触发数组移动,但不触发扩容逻辑) - ⚠️ 注意:
ensureCapacity()参数是「最小需要容量」,不是「精确容量」;传 100 不代表数组长度变成 100,只是确保 ≥100;若当前已 ≥100,则无操作
和构造函数 initialCapacity 比,有啥区别?
本质一样,都是设初始数组大小,但时机不同:new ArrayList(100) 在实例化时就分配好数组;ensureCapacity(100) 是对已有实例“事后扩容”。实际性能差异极小,但语义更清晰:
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
立即学习“Java免费学习笔记(深入)”;
- 构造时就知道规模 → 用
new ArrayList(initialCapacity) - 实例已存在、稍后才确定要塞多少数据 → 用
ensureCapacity() - 二者都只影响扩容起点,不影响
size()(即逻辑元素个数),调用后size()仍是 0
示例:
ArrayList<String> list = new ArrayList<>();
list.ensureCapacity(5000); // 底层数组现在至少长 5000
for (int i = 0; i < 5000; i++) {
list.add("item" + i); // 全程无 resize
}
容易被忽略的坑:ensureCapacity() 不解决并发和结构性修改问题
即使你精准调用了 ensureCapacity(),以下情况仍会触发意外开销或异常:
- 多线程同时
add()→ 即使容量足够,也可能因竞争导致重复扩容(ArrayList 非线程安全) - 用
list.iterator().remove()或removeIf()删除大量元素 → 数组不变,但后续add()仍按当前size()判断是否扩容,可能造成严重空间浪费 - 调用
clear()后继续 add → 底层数组没变,但size归零;如果之后只加几十个元素,那之前预分配的 5000 就纯属闲置内存
所以,预分配只是优化手段之一,不能替代对使用模式的判断。真正高频增删的场景,该换 LinkedList(插入删除快)、或上 ArrayDeque(作为栈/队列更高效)、甚至考虑对象池 —— 而不是死磕 ensureCapacity()。

















