arraylist核心是存、扩、取三件事:elementdata存数据,size记录元素数,default_capacity=10但懒加载;add触发grow扩容至1.5倍,get为o(1),remove因数组移动为o(n)。

直接看源码容易迷失在细节里,关键是要带着问题、按主线切入。ArrayList 的核心就三件事:怎么存、怎么扩、怎么取。围绕这三点读,效率最高。
先抓主干结构:三个字段定乾坤
打开 ArrayList 类,第一眼盯住这三个成员变量:
- transient Object[] elementData:真正装数据的底层数组,所有增删改查都围着它转;
-
private int size:当前已存多少个有效元素,不是数组长度,调用
size()返回的就是它; -
DEFAULT_CAPACITY = 10:默认容量值,但注意——JDK 8+ 的无参构造并不立刻分配长度为 10 的数组,而是指向一个共享空数组,等第一次
add才真正扩容。
理解这三者的关系,就抓住了整个类的数据骨架。
重点读 add() 和 grow():扩容逻辑藏在这里
扩容是 ArrayList 最具特征的行为,别从构造器开始读,直接跳到 add(E e) 方法:
- 它内部调用私有方法
add(e, elementData, size),其中关键判断是:if (s == elementData.length)—— 元素个数等于数组长度,说明满了; - 满则调用
grow(),这个方法才是真正扩容的核心:新容量 = 旧容量 + 旧容量 / 2(即 1.5 倍),用位运算实现:oldCapacity + (oldCapacity >> 1); - 扩容后用
Arrays.copyOf()把老数组内容复制过去,再替换elementData引用。
这个流程反复出现,读通这一段,就懂了“动态数组”怎么动态起来的。
对照构造器看初始化策略:懒加载是关键差异
对比三种构造方法,能看出设计意图:
-
无参构造:
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,不占内存,首次 add 才触发扩容到 10; -
指定容量构造:
new Object[initialCapacity],立刻分配,适合预估数据量的场景; - 集合构造:根据传入集合大小设初始容量,避免刚创建就扩容。
这种“能晚就不早”的懒加载,是 JDK 8 对内存友好性的优化,也是面试常考点。
结合 get() 和 remove() 理解连续内存的代价与优势
随机访问快,是因为 get(int index) 直接返回 elementData[index],O(1);
但 remove(int index) 就暴露了物理连续的代价:
- 先检查下标合法性;
- 把待删位置之后的所有元素向前挪一位(
System.arraycopy(...)); - 最后把末尾位置设为
null(防止内存泄漏,尤其存的是大对象时)。
这段移动逻辑,就是顺序表插入/删除 O(n) 时间复杂度的来源,也是它和 LinkedList 的根本分水岭。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











