ArrayList底层核心是elementData数组与size变量分工:前者存储元素,后者记录实际个数;扩容在add时触发,公式为newCapacity = oldCapacity + (oldCapacity >> 1),即1.5倍增长。

掌握 ArrayList 底层源码的核心精髓,关键不在逐行通读,而在于抓住三条主线:数据结构本质、扩容触发逻辑、操作性能代价。下面从实战角度拆解最值得深挖的几个点。
看懂 elementData 和 size 的分工
elementData 是 Object[] 类型的真实存储容器,size 是当前已存元素个数——这两个字段的分离,直接决定了所有行为逻辑。
- 数组长度(capacity) = elementData.length,它可能远大于 size,比如刚扩容完但只加了 2 个元素
- add() 判断是否要扩容,依据是 size == elementData.length,不是看“还剩多少空位”
- get(int index) 只校验 index
搞清扩容是怎么一步步发生的
扩容不是黑盒,它发生在 add() 过程中,且分阶段处理不同初始化状态。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 无参构造时,elementData 指向 DEFAULTCAPACITY_EMPTY_ELEMENTDATA(空数组),首次 add 才真正分配长度为 10 的数组
- 后续扩容公式是:newCapacity = oldCapacity + (oldCapacity >> 1),即 1.5 倍,但会做边界检查(不超过 MAX_ARRAY_SIZE)
- elementData 被声明为 transient,意味着序列化时不保存整个数组,只保存 size 和实际元素,避免序列化大量 null 占位空间
理解增删操作背后的真实开销
随机访问快是表象,中间/头部增删慢才是关键约束,必须从数组移动逻辑出发看源码。
- add(int index, E element):先校验索引,再用 System.arraycopy 把 index 及之后的元素整体后移一位
- remove(int index):同样调用 arraycopy,把 index+1 开始的所有元素前移,末尾位置留空(不会自动置 null,但 GC 可回收)
- remove(Object o):遍历查找,找到第一个匹配项后再执行上述移动,最坏 O(n) 时间
对比三种构造方式的实际影响
构造方式看似简单,却直接影响内存分配节奏和扩容次数。
- ArrayList
list = new ArrayList():懒加载,节省初始内存,适合不确定大小的场景 - ArrayList
list = new ArrayList(1000):直接分配 1000 长度数组,避免前几百次 add 触发多次扩容,适合预估容量的批量导入 - ArrayList
list = new ArrayList(0):elementData 指向 EMPTY_ELEMENTDATA,首次 add 直接按需扩容(如加 1 个就扩成 1),不强制到 10
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










