Java里ArrayList扩容机制是怎样的_Java容量增长原理说明

ArrayList扩容触发条件是执行add或addAll等操作且size等于elementData.length;默认扩容为1.5倍,即newCapacity = oldCapacity + (oldCapacity >> 1),再与minCapacity和MAX_ARRAY_SIZE比较取合适值。

ArrayList扩容触发条件是什么

扩容发生在执行 addaddAll 等修改结构的操作时,且当前元素个数 size 已等于底层数组 elementData.length。注意:构造时指定初始容量(如 new ArrayList(10))不会触发扩容,只有真正“放不下”时才触发。

默认扩容公式是怎样的

Java 8+ 的扩容逻辑是:newCapacity = oldCapacity + (oldCapacity >> 1),即扩大为原容量的 1.5 倍(右移 1 位等于除以 2)。但这个值不是最终结果——它还要和最小需要容量 minCapacity 比较,取较大者;再与 MAX_ARRAY_SIZEInteger.MAX_VALUE - 8)比较,防止溢出。

int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);

为什么扩容后不是直接翻倍

1.5 倍是权衡空间利用率与复制开销的结果。翻倍(×2)会导致内存浪费更明显,尤其在频繁 add 后又长期不增长的场景;而 1.5 倍能在多数情况下减少扩容次数,同时控制数组膨胀节奏。另外,JVM 数组分配对大对象有额外开销,过快增长可能触发更多 GC。

  • 初始容量为 10 → 扩容后为 15 → 再扩容为 22 → 接着是 33、49…
  • 若用 ×2,10→20→40→80,同样存 50 个元素需 4 次扩容;1.5 倍只需约 3 次(10→15→22→33→49)
  • ensureCapacity 可主动预扩容,避免运行时多次 copy

扩容时的数组复制开销怎么评估

每次扩容都会调用 Arrays.copyOf,本质是 System.arraycopy,时间复杂度 O(n),其中 n 是旧数组长度。这意味着:频繁在末尾 add 小量数据(如循环逐个 add)比一次性 addAll 多次触发扩容更慢。

  • 避免在循环内反复 add:改用 new ArrayList(expectedSize) 预设容量
  • 已知要加 1000 个元素,却用无参构造,会经历约 10 次扩容(10→15→22→33→49→73→109→163→244→366→549→

    823)
  • trimToSize() 可释放冗余空间,但仅在确定不再增容后调用,否则后续 add 又触发扩容

实际开发中,最容易被忽略的是:扩容判断只看 size == elementData.length,不关心是否删除过元素。也就是说,即使你调用过 removesize 变小,只要没手动 trimToSize,底层数组依然维持最大扩容后的长度——这会影响内存占用,尤其在长生命周期的 list 中反复增删时。