ensureCapacity有用但仅适用于可预估大量元素的场景,它提前分配数组空间避免多次扩容复制,不改变size也不触发扩容,传负数抛异常、传0合法但无效。

ArrayList的ensureCapacity到底有没有用
有用,但只在明确知道后续要塞大量元素时才值得调。它不改变当前size,也不触发扩容逻辑,只是提前把内部数组elementData拉到指定长度——避免后续add时反复拷贝。
常见错误是以为调了ensureCapacity就能“提速10倍”,结果发现没差多少:因为单次add本身开销极小,真正拖慢的是多次扩容引发的数组复制。比如从空开始add 10000个元素,默认扩容约14次;而提前ensureCapacity(10000)后,一次都不扩。
- 适用场景:批量加载(如读文件、DB查出万级记录后逐个add)
- 不适用场景:边处理边add、元素数量完全不可预估、或只add几十个
- 注意:
ensureCapacity参数是“最小容量”,不是“最终大小”;它不会截断已有空间
为什么ensureCapacity不接受负数但能传0
传负数会直接抛IllegalArgumentException,这是JDK源码里硬写的校验;传0则合法,且无实际效果——内部判断minCapacity 就直接返回,不做任何操作。
容易踩的坑是误用Math.max(0, expectedSize)兜底后传进ensureCapacity,结果白调一次。更典型的是从配置读数字时没校验空值或负值,导致启动就崩在这一行。
立即学习“Java免费学习笔记(深入)”;
- 安全写法:
if (expectedSize > 0) list.ensureCapacity(expectedSize); - 别依赖“传0没事”来省条件判断,逻辑上该保护的地方不能偷懒
- 注意:
ensureCapacity是public方法,但ArrayList没提供反向查询当前capacity的API
预分配比默认扩容快多少?看真实拷贝次数
默认构造的ArrayList初始容量是10,扩容策略是oldCapacity + (oldCapacity >> 1)(即1.5倍)。从0加到10000个元素,实际发生14次数组复制,总拷贝元素数约26000+次(每次复制要把旧数组全搬过去)。
而提前ensureCapacity(10000),拷贝次数为0——前提是这10000个元素确实是连续add的。如果中间穿插了remove或clear,那预分配的空间可能长期闲置,浪费堆内存。
- 性能差异主要体现在GC压力和CPU缓存局部性:少复制 = 少临时对象 = 更少Young GC
- 但别为了省几次复制去预分配100MB——内存占用也是成本
- 对比
new ArrayList(10000):效果等价于ensureCapacity(10000),只是写法更简洁
替代方案:什么情况下不该用ensureCapacity
当你要add的元素本身创建代价高(比如new一个大对象),或者add逻辑包含IO/锁/网络调用时,预分配内存毫无意义——瓶颈根本不在数组拷贝上。
另一个典型场景是用ArrayList做队列(频繁remove(0)或add(0)),此时无论怎么预分配,每次头插/头删都要移动后续所有元素,复杂度O(n),这时候该换ArrayDeque或LinkedList(虽然后者也有坑)。
- 如果集合最终要转成数组(
toArray()),预分配对它没影响——toArray自己会按size新建数组 - 多线程环境别指望
ensureCapacity帮你解决并发问题;它不是线程安全的 - Android上注意低版本ART对大数组分配的额外开销,预分配过大可能触发GC pause
预分配这件事,核心就看两点:你能不能靠谱地预估数量,以及这个数量是不是真够大到让扩容拷贝成为瓶颈。其他都是枝节。










