ArrayList 源码深度分析(基于 JDK 8)
ArrayList 是 Java 集合框架中最常用的动态数组实现 ,继承自 AbstractList,实现了 List、RandomAccess、Cloneable、Serializable 接口,核心特点是支持随机访问、动态扩容、非线程安全,适用于「查询多、增删少」的场景。
一、类结构与核心接口
先看 ArrayList 的类定义,理解其继承体系和核心能力:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// ... 源码内容
}
各接口的作用:
-
List<E>:核心列表接口,定义了增删改查、遍历等基础操作(如add、remove、get)。 -
RandomAccess:标记接口 (无抽象方法),表明该集合支持「随机访问」(通过索引直接获取元素,时间复杂度 O (1)),用于优化遍历效率(如Collections.binarySearch会根据是否实现该接口选择不同遍历方式)。 -
Cloneable:支持调用clone()方法实现「浅克隆」。 -
Serializable:支持序列化(通过自定义writeObject/readObject优化序列化效率)。
二、核心成员变量
ArrayList 的底层是数组,核心变量决定了其存储特性和扩容逻辑:
// 1. 默认初始容量(无参构造时,第一次 add 才初始化)
private static final int DEFAULT_CAPACITY = 10;
// 2. 空数组(用户指定容量为 0 或传入空集合时使用)
private static final Object[] EMPTY_ELEMENTDATA = {};
// 3. 默认空数组(无参构造专用,与 EMPTY_ELEMENTDATA 区分,用于第一次扩容判断)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 4. 存储元素的核心数组(transient 修饰:不参与默认序列化,自定义序列化优化)
transient Object[] elementData;
// 5. 集合中实际元素个数(≠ elementData 数组长度)
private int size;
// 6. 最大容量(避免扩容时数组长度溢出,Integer.MAX_VALUE - 8 是因为虚拟机对数组头有额外存储)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
关键区分:EMPTY_ELEMENTDATA vs DEFAULTCAPACITY_EMPTY_ELEMENTDATA
-
无参构造
new ArrayList<>():elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,第一次add时直接扩容到DEFAULT_CAPACITY(10)。 -
指定容量为 0
new ArrayList<>(0)或传入空集合new ArrayList<>(Collections.emptyList()):elementData = EMPTY_ELEMENTDATA,第一次add时扩容到 1(而非 10)。
三、构造方法
ArrayList 提供 3 个核心构造方法,对应不同初始化场景:
1. 无参构造(最常用)
public ArrayList() {
// 初始化时不分配容量,第一次 add 才扩容到 10
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
懒加载设计:避免无参创建后不使用导致的内存浪费。
2. 指定初始容量构造
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 容量 >0:直接创建对应长度的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 容量 =0:复用空数组 EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
优化场景:已知集合大致大小(如 1000 个元素),直接指定容量可避免多次扩容,提升性能。
3. 传入集合构造
public ArrayList(Collection<? extends E> c) {
// 把集合转为数组
elementData = c.toArray();
// 更新实际元素个数
if ((size = elementData.length) != 0) {
// 若数组类型不是 Object[],则拷贝为 Object[](避免类型转换异常)
if (elementData.getClass() != Object[].class) {
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
} else {
// 集合为空:复用 EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
}
}
注意 :c.toArray() 可能返回非 Object[] 类型(如 String[]),需通过 Arrays.copyOf 转为 Object[],保证 ArrayList 底层数组类型统一。
四、核心方法解析
(一)添加元素:add(E e) 与 add(int index, E element)
添加元素是 ArrayList 的核心操作,涉及「容量检查」和「扩容」逻辑。
1. 尾部添加:add(E e)(时间复杂度 O (1),扩容时 O (n))
public boolean add(E e) {
// 1. 确保容量(核心:判断是否需要扩容)
ensureCapacityInternal(size + 1); // size+1 是本次添加所需的最小容量
// 2. 元素存入数组,size 自增
elementData[size++] = e;
return true;
}
2. 指定索引添加:add(int index, E element)(时间复杂度 O (n))
public void add(int index, E element) {
// 1. 检查索引合法性(index 必须在 [0, size] 之间)
rangeCheckForAdd(index);
// 2. 确保容量
ensureCapacityInternal(size + 1);
// 3. 移动数组元素:从 index 开始的元素向后移 1 位(核心开销来源)
System.arraycopy(elementData, index, elementData, index + 1, size - index);
// 4. 插入元素,size 自增
elementData[index] = element;
size++;
}
关键 :System.arraycopy 是 native 方法,效率较高,但移动元素的本质是「拷贝」,元素越多(size - index 越大),开销越大,这也是 ArrayList 插入慢的原因。
(二)扩容核心逻辑:ensureCapacityInternal → ensureExplicitCapacity → grow
扩容是 ArrayList 动态数组的核心,触发条件:size + 1 > elementData.length(添加元素后超出当前数组容量)。
1. 计算最小容量:calculateCapacity
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 若为无参构造的初始状态(DEFAULTCAPACITY_EMPTY_ELEMENTDATA),最小容量取 10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 其他情况:最小容量 = 所需容量(size+1)
return minCapacity;
}
2. 检查是否需要扩容:ensureExplicitCapacity
private void ensureExplicitCapacity(int minCapacity) {
// modCount:记录集合修改次数(用于 fail-fast 机制)
modCount++;
// 最小容量 > 数组长度 → 触发扩容
if (minCapacity - elementData.length > 0) {
grow(minCapacity);
}
}
3. 实际扩容:grow(核心方法)
private void grow(int minCapacity) {
// 1. 获取当前数组长度
int oldCapacity = elementData.length;
// 2. 扩容公式:新容量 = 旧容量 + 旧容量/2(即 1.5 倍扩容)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 3. 处理特殊情况:
// - 若旧容量为 0(如 new ArrayList<>(0)),newCapacity 会是 0,需修正为 minCapacity(1)
// - 若扩容后溢出(newCapacity < 0),直接用最大容量 MAX_ARRAY_SIZE
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}
// 4. 拷贝原数组元素到新数组(扩容的本质:创建新数组 + 拷贝元素)
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 处理超大容量需求
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) { // 溢出(minCapacity 为负数)
throw new OutOfMemoryError();
}
// 最小容量超过 MAX_ARRAY_SIZE → 用 Integer.MAX_VALUE,否则用 MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
扩容关键结论:
-
默认扩容为原容量的 1.5 倍(位运算
oldCapacity >> 1比除法oldCapacity / 2效率高)。 -
扩容本质是「创建新数组 + 拷贝元素」,时间复杂度 O (n),频繁扩容会影响性能(建议已知大小时光指定初始容量)。
-
特殊场景:初始容量为 0 时,第一次 add 扩容到 1,第二次 add 扩容到 1.5(取整为 1)→ 仍不够,扩容到 2,第三次到 3,第四次到 4,以此类推。
(三)删除元素:remove(int index) 与 remove(Object o)
删除元素同样需要移动数组,时间复杂度 O (n)。
1. 按索引删除:remove(int index)
public E remove(int index) {
// 1. 检查索引合法性(index < size)
rangeCheck(index);
modCount++;
// 2. 保存要删除的元素(返回用)
E oldValue = elementData(index);
// 3. 计算需要移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0) {
// 从 index+1 开始的元素向前移 1 位(覆盖被删除元素)
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
// 4. 释放最后一个元素的引用(避免内存泄漏)
elementData[--size] = null;
return oldValue;
}
2. 按元素删除:remove(Object o)
public boolean remove(Object o) {
if (o == null) {
// 遍历查找 null 元素(ArrayList 允许 null)
for (int index = 0; index < size; index++) {
if (elementData[index] == null) {
fastRemove(index); // 快速删除(无索引检查,无返回值)
return true;
}
}
} else {
// 遍历查找 equals 匹配的元素
for (int index = 0; index < size; index++) {
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
}
return false;
}
// 快速删除:跳过索引检查和返回值,优化性能
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
elementData[--size] = null;
}
注意 :remove(Object o) 只删除「第一个匹配的元素」,且 null 元素通过 == 判断,非 null 元素通过 equals 判断。
(四)查询与修改:get(int index) 与 set(int index, E element)
因底层是数组,支持随机访问,查询和修改的时间复杂度均为 O (1):
// 查询元素
public E get(int index) {
rangeCheck(index); // 检查索引 < size
return elementData(index); // 直接返回数组对应位置元素((E) elementData[index])
}
// 修改元素
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element; // 直接覆盖数组元素
return oldValue;
}
五、序列化优化
ArrayList 的 elementData 被 transient 修饰,说明不参与 JVM 默认序列化。为何要这么设计?
- 因为
elementData数组的长度可能大于实际元素个数(扩容后未填满),默认序列化会把空元素也序列化,浪费空间。
ArrayList 通过自定义序列化 / 反序列化方法优化:
// 序列化:只写入实际存在的元素(size 个)
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
// 1. 写入 modCount(用于反序列化时校验)
int expectedModCount = modCount;
s.defaultWriteObject();
// 2. 写入实际元素个数
s.writeInt(size);
// 3. 逐个写入元素(只写前 size 个,跳过空元素)
for (int i = 0; i < size; i++) {
s.writeObject(elementData[i]);
}
// 4. 校验序列化过程中集合是否被修改(fail-fast)
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
// 反序列化:恢复元素到数组
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// 1. 读取非 transient 变量(如 size、modCount)
s.defaultReadObject();
// 2. 读取元素个数
int size = s.readInt();
if (size > 0) {
// 3. 计算容量(若为无参构造初始化,容量取 10;否则取 size)
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
// 4. 初始化数组并读取元素
elementData = new Object[capacity];
for (int i = 0; i < size; i++) {
elementData[i] = s.readObject();
}
}
}
六、迭代器与 Fail-Fast 机制
ArrayList 的迭代器 Itr 是内部类,实现了 Iterator 接口,核心是「快速失败(Fail-Fast)」机制:遍历过程中若集合被结构性修改(add/remove 等),直接抛出 ConcurrentModificationException,避免数据不一致。
1. 迭代器核心变量
private class Itr implements Iterator<E> {
int cursor; // 下一个要返回的元素索引
int lastRet = -1; // 上一个返回的元素索引(-1 表示未返回或已删除)
// 期望的修改次数(初始等于集合的 modCount)
int expectedModCount = modCount;
// ... 方法实现
}
2. Fail-Fast 触发逻辑
迭代器的所有操作(next()、remove())都会先调用 checkForComodification():
final void checkForComodification() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
-
集合结构性修改时(add/remove),
modCount会自增。 -
若迭代器的
expectedModCount与modCount不一致,说明集合被外部修改,触发异常。
3. 迭代器的 remove() 方法(安全删除)
普通 remove(int index) 会导致 modCount 自增,触发 Fail-Fast,但迭代器的 remove() 不会:
public void remove() {
if (lastRet < 0) {
throw new IllegalStateException();
}
checkForComodification();
try {
// 调用集合的 remove 方法
ArrayList.this.remove(lastRet);
// 更新 cursor(避免跳过元素)
cursor = lastRet;
lastRet = -1;
// 同步 expectedModCount 与 modCount
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
关键 :删除后会更新 expectedModCount = modCount,保证后续操作不会触发异常。
七、其他重要方法
1. trimToSize():缩容优化
将数组长度缩为实际元素个数,节省内存(适用于集合不再添加元素的场景):
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
}
}
2. ensureCapacity(int minCapacity):手动扩容
提前指定最小容量,避免多次自动扩容(如批量添加大量元素时):
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
3. subList(int fromIndex, int toIndex):子列表视图
返回原集合的「子列表视图」,而非新集合:修改子列表会影响原集合,反之亦然,且子列表的迭代器也遵循 Fail-Fast 机制。
八、ArrayList 核心特性总结
| 特性 | 说明 |
|---|---|
| 底层实现 | 动态数组(Object []) |
| 访问效率 | 随机访问 O (1)(直接索引) |
| 增删效率 | 尾部添加 O (1)(无扩容),插入 / 删除 O (n)(需移动元素) |
| 容量机制 | 初始容量 10(无参构造),默认扩容 1.5 倍 |
| 线程安全 | 非线程安全(多线程操作需手动同步) |
| 元素限制 | 允许 null 元素,允许重复元素 |
| 遍历机制 | 支持迭代器、增强 for、普通 for(推荐普通 for 遍历,效率最高) |
| 序列化 | 自定义序列化,只序列化实际元素 |
| 故障机制 | Fail-Fast(遍历中修改集合抛异常) |
九、适用场景与注意事项
1. 适用场景
-
频繁查询(
get)、尾部添加(add(E e))的场景。 -
已知集合大小,可指定初始容量优化性能。
2. 注意事项
-
非线程安全:多线程环境下需使用
Collections.synchronizedList(new ArrayList<>())或CopyOnWriteArrayList。 -
避免频繁插入 / 删除:若需频繁在中间位置增删,建议使用
LinkedList(双向链表,增删 O (1),查询 O (n))。 -
扩容开销:批量添加元素时,提前调用
ensureCapacity减少扩容次数。 -
Fail-Fast 不是线程安全保证:仅用于开发调试,不能依赖其处理并发修改(并发场景需用线程安全集合)。
总结
ArrayList 的设计核心是「动态数组 + 高效随机访问」,通过「1.5 倍扩容」「懒加载初始化」「自定义序列化」等优化兼顾性能和内存效率。其源码的关键亮点包括:
-
区分
DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA,优化无参构造和零容量构造的扩容逻辑。 -
用
transient修饰数组,自定义序列化减少空间浪费。 -
Fail-Fast 机制通过
modCount保证遍历安全性。 -
核心操作(扩容、移动元素)依赖
Arrays.copyOf和System.arraycopy(native 方法,效率高)。