java动态数组以固定长度数组为底层,通过扩容缩容机制实现弹性存储;扩容时新建2倍长数组并复制元素,缩容阈值设为1/4防抖动;add/remove操作需严格校验索引并移动元素以维护size与物理位置一致。

Java 数组本身长度固定,无法直接伸缩;所谓“动态数组”,其实是用基础数组作为底层容器,配合容量管理、元素迁移与自动扩容缩容逻辑,构建出具备弹性存储能力的数据结构。
为什么必须用数组做底层?
数组提供连续内存和 O(1) 索引访问能力,是实现高效随机读写的物理基础。所有 Java 动态集合(如 ArrayList)都依赖它——不是抛弃数组,而是封装数组。
关键点:
- 数组长度(length)是 final 字段,初始化后不可变
- 真正可变的是“已存元素个数”(常命名为 size),它 ≤ length
- 动态行为发生在 size == length(满)或 size ≤ length/4(过空)时
扩容与缩容的核心逻辑
扩容不是“拉长原数组”,而是创建新数组、复制数据、替换引用;缩容同理,目的是控制空间冗余度。
典型策略:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 扩容:当 size == data.length,新建长度为 2 × data.length 的数组,用
System.arraycopy搬运全部元素 - 缩容:当 size > 0 且 size ≤ data.length / 4,新建长度为 data.length / 2 的数组(最小不低于初始容量,如 10)
- 避免频繁抖动:缩容阈值设为 1/4 而非 1/2,形成“扩容容易、缩容谨慎”的滞后调节机制
索引操作如何保持一致性?
add(int index, E e) 和 remove(int index) 是动态数组最易出错的环节,核心在于维护 size 与元素物理位置的同步。
添加时(从后往前移):
- 检查 index 合法性:必须满足 0 ≤ index ≤ size(允许插在末尾)
- 从 size−1 开始,逐个将 data[i] 复制到 data[i+1],腾出 index 位置
- 赋值后执行 size++
删除时(从前向后覆):
- 检查 index 合法性:必须满足 0 ≤ index
- 从 index+1 开始,将 data[i] 覆盖到 data[i−1],覆盖至 size−1 位置
- 置 data[size−1] = null 防止内存泄漏,再执行 size−−
何时该用自己写的动态数组?
日常开发中优先使用 ArrayList —— 它已高度优化,支持 fail-fast、线程不安全但性能高。自己实现仅适用于以下场景:
- 教学理解底层原理(如算法课、数据结构实验)
- 嵌入式或内存受限环境,需精简无额外功能的轻量容器
- 定制化需求,例如按访问频次自动重排、带版本快照、只读视图隔离等
不要为了“看起来更底层”而重复造轮子;重点是理解 size/length 分离、扩容触发时机、索引边界处理这三层逻辑。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










