java原生数组长度不可变,需通过动态扩容机制解决;正确扩容触发点是size+1>array.length,推荐1.5倍扩容策略newcapacity=oldcapacity+(oldcapacity>>1)。

原生数组长度不可变是 Java 开发中一个基础但高频的痛点——它在编译期就锁死容量,一旦写满就无法追加,强行操作会抛 ArrayIndexOutOfBoundsException。生产环境里,这常导致服务卡顿、日志截断、批量任务失败等隐性故障。真正有效的解法不是回避数组,而是用动态扩容机制把它“用活”。核心在于三点:明确区分 逻辑大小(size)和物理容量(capacity)、扩容时同步更新所有关联状态、复制过程避免越界或默认值污染。
识别真实扩容触发点,别只看 size == capacity
很多自定义动态数组类错误地把 isFull() 写成 size == array.length,看似合理,但忽略了插入前校验的时机。正确做法是:在 add(element) 执行赋值前,先判断 size + 1 > array.length。因为你要塞第 size+1 个元素,而数组最大下标只能到 array.length - 1。若等到 size == array.length 才触发,下一次 add 必然越界。
- ✅ 正确示例:
if (size + 1 > array.length) resize(); - ❌ 常见错误:
if (size == array.length) resize();—— 此时已无空位,resize 后仍会立即再次触发 - ⚠️ 注意:
size是当前有效元素个数,array.length是当前堆内存分配的总格子数,二者语义不能混用
扩容计算要留余量,别硬写 *2 或 +1
盲目翻倍(new int[array.length * 2])或只加 1(new int[array.length + 1])都不适合生产场景。前者在小数据量时浪费严重(如从 5 扩到 10,实际只用 6 个);后者在高频写入时导致频繁扩容(每加 1 个就扩 1 次),时间复杂度退化为 O(n²)。ArrayList 的 1.5 倍策略(old + (old >> 1))是经过权衡的工业级方案:既控制扩容频次,又避免内存陡增。
- 推荐公式:
int newCapacity = oldCapacity + (oldCapacity >> 1);(即 ×1.5) - 兜底保障:
if (newCapacity (比如批量导入时预估需 5000 条,就直接扩到 5000) - 避免整数溢出:
if (newCapacity
复制数据必须按 size 遍历,而非 array.length
扩容后新数组长度变大,但旧数据只占前 size 个位置。如果遍历整个 newArray.length 打印或序列化,会输出大量 0(int)、null(Object)等默认值,干扰日志分析或下游解析。更严重的是,某些业务逻辑误把未填充位置当有效数据处理,引发空指针或数值异常。
- ✅ 复制时:
System.arraycopy(oldArray, 0, newArray, 0, size); - ✅ 打印/导出时:
Arrays.toString(Arrays.copyOf(array, size)); - ❌ 错误示范:
for (int i = 0; i
状态字段必须原子更新,尤其多线程场景
在并发写入的模块(如日志缓冲区、实时指标聚合器)中,仅更新数组引用不够。若 size 字段未用 volatile 修饰或未同步更新,其他线程读到的仍是旧 size,可能重复写入同一位置,或跳过刚写入的数据。ArrayList 虽非线程安全,但其内部字段设计可参考:容量由 elementData.length 事实决定,size 单独维护且所有修改走同一临界区。
- 单线程:确保
array = newArray;和this.size = newSize;成对出现,不被拆开 - 多线程:用
AtomicInteger size+ CAS 更新,或整个add方法加锁 - 进阶方案:像 C 内存池那样,扩容前用
compareAndSet校验当前容量是否匹配,防竞态扩容










