
ArrayList 的扩容系数设为 1.5 倍,是空间效率与时间效率之间权衡的结果,并非数学最优,而是工程实践中的合理折中。
为什么不是 2 倍?
2 倍扩容虽简化计算(位运算即可),但容易造成内存浪费:
假设初始容量 10,连续扩容:10 → 20 → 40 → 80 → 160……每次新增一倍,旧数组中已存元素只占新数组一半,闲置空间持续累积;尤其在集合长期稳定、不再大幅增长时,高位容量长期空置,GC 压力和堆内存碎片增加;对大对象(如存储大量字符串或自定义对象)而言,一次翻倍可能直接触发老年代分配或 Full GC。
为什么不是 1.2 或 1.8?
1.5 是兼顾摊还成本与内存利用率的经验值:
摊还分析:按 1.5 倍扩容,插入 n 个元素的总拷贝次数约为 2n(几何级数求和),均摊 O(1);1.2 倍虽更省空间,但扩容频次显著上升,拷贝开销变大;1.8 倍则接近 2 倍的浪费问题;整数运算友好:oldCapacity + oldCapacity >> 1 等价于 oldCapacity * 1.5,仅用位移+加法,无浮点或除法,JVM 可高效优化;历史兼容性:从 JDK 1.2 沿用至今,类库生态(如 subList、迭代器行为)均基于该增长节奏设计。
实际扩容过程的关键细节
扩容不是简单乘以 1.5,而是有兜底与校验逻辑:
新容量 = oldCapacity + (oldCapacity >> 1),即 oldCapacity * 1.5 向下取整(因右移是整数除法);若计算结果 ≤ 0(如 oldCapacity 为负溢出),则使用 Integer.MAX_VALUE;若所需最小容量 minCapacity > 计算所得 newCapacity,则直接取 minCapacity(例如构造时指定 huge initialCapacity 或 addAll 超大集合);最终容量还会被限制在 MAX_ARRAY_SIZE = Integer.MAX_VALUE – 8(部分 JVM 预留数组元数据空间)。
对开发者的启示
理解 1.5 倍背后逻辑,有助于写出更高效的代码:
明确预期大小时,优先使用 new ArrayList(initialCapacity),避免多次扩容拷贝;批量添加前可调用 ensureCapacity(minCapacity) 预分配;注意 size() 和 capacity()(后者需反射或通过估算获取)的区别——容量不等于已用空间;在内存敏感场景(如 Android 或实时系统),可考虑替代实现(如 Trove、Eclipse Collections)或手动控制扩容节奏。

评论(0)