JDK8 ArrayList 源码深度解析
作为 JDK 集合框架中最常用的 List 实现类,ArrayList 几乎是每个 Java 开发者日常开发的 “标配”。它看似简单易用,但其底层的动态数组设计、扩容机制、迭代器实现等细节,藏着很多提升代码效率的关键逻辑。本文基于 JDK8 版本,从源码角度拆解 ArrayList 的核心实现,帮你彻底搞懂它的 “底层逻辑”。
一、ArrayList 核心定位
先明确 ArrayList 的核心特征,这是理解源码的基础:
底层结构:基于动态数组实现(数组容量可自动扩容);
核心特性:有序、可重复、支持索引快速访问,查询效率高(O (1))、增删效率低(尤其是中间位置,O (n));
线程安全:非线程安全(方法未加锁),多线程环境下需手动同步(如
Collections.synchronizedList);JDK8 优化:无重大结构变更,但优化了扩容时的数组复制效率、新增
removeIf等方法。
二、核心成员变量(源码关键)
先看 ArrayList 类中定义的核心变量,这些是理解后续逻辑的关键:
// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组(用于空实例初始化)
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认空数组(用于无参构造,区别于EMPTY_ELEMENTDATA,标记首次添加元素时扩容至10)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储元素的核心数组(transient表示序列化时忽略,自定义序列化逻辑)
transient Object[] elementData;
// 集合中实际元素个数(区别于数组容量)
private int size;
// 数组最大容量(避免OOM,Integer.MAX_VALUE - 8)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;关键区分:EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是 JDK8 对空数组的精细化设计,目的是区分 “用户指定容量为 0” 和 “无参构造” 两种场景,保证首次添加元素时的扩容逻辑统一。
三、构造方法解析
ArrayList 提供了 3 种构造方法,核心是初始化 elementData 数组:
1. 无参构造(最常用)
public ArrayList() {
// 初始化为默认空数组,首次添加元素时才扩容至10
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}注意:JDK8 中无参构造不再直接初始化容量为 10 的数组,而是延迟到首次添加元素时,减少空实例的内存占用。
2. 指定初始容量
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 初始化指定容量的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 容量为0时使用EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}最佳实践:如果能预估元素个数,优先使用此构造方法指定容量,避免后续频繁扩容。
3. 基于集合构造
public ArrayList(Collection<? extends E> c) {
// 集合转数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 修复c.toArray()可能返回非Object[]的问题(JDK8兼容处理)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 空集合则赋值为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}四、核心方法源码解析
1. 添加元素:add (E e)
添加元素是 ArrayList 最核心的操作,涉及 “容量检查→扩容→赋值” 三步:
public boolean add(E e) {
// 1. 确保数组容量足够(size+1)
ensureCapacityInternal(size + 1);
// 2. 元素赋值到数组末尾,size自增
elementData[size++] = e;
return true;
}
// 第一步:计算最小容量
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 区分“默认空数组”和“用户指定空数组”的容量计算
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果是无参构造的默认空数组,最小容量取10和minCapacity的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// 第二步:判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 记录修改次数(快速失败机制)
// 最小容量 > 数组当前长度 → 触发扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 第三步:核心扩容逻辑
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 扩容公式:新容量 = 旧容量 + 旧容量/2(即1.5倍)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 处理扩容后仍不足的情况(如初始容量0,首次扩容需到10)
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 处理超大容量(超过MAX_ARRAY_SIZE)
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 数组复制(核心:Arrays.copyOf底层是System.arraycopy,native方法,效率高)
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 处理超大容量的边界情况
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 溢出
throw new OutOfMemoryError();
// 超过MAX_ARRAY_SIZE则取Integer.MAX_VALUE,否则取MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}核心总结:
扩容倍数:JDK8 中固定为 1.5 倍(位运算
oldCapacity >> 1等价于除以 2,效率更高);扩容本质:创建新数组 + 复制原数组元素,是耗时操作,所以预估容量很重要;
modCount:记录集合修改次数,迭代器遍历会校验此值,不一致则抛
ConcurrentModificationException。
2. 指定位置添加:add (int index, E element)
public void add(int index, E element) {
// 校验索引合法性(index < 0 或 > size 抛异常)
rangeCheckForAdd(index);
// 容量检查
ensureCapacityInternal(size + 1);
// 核心:数组元素后移(System.arraycopy是native方法,效率优于手动循环)
System.arraycopy(elementData, index, elementData, index + 1, size - index);
// 赋值到指定位置
elementData[index] = element;
size++;
}
性能痛点:此方法需要移动 size - index 个元素,索引越靠前,移动元素越多,效率越低(这也是 ArrayList 增删慢的核心原因)。
3. 获取元素:get (int index)
public E get(int index) {
// 校验索引(index >= size 抛异常)
rangeCheck(index);
// 直接返回数组指定位置元素(O(1)效率,ArrayList的核心优势)
return elementData(index);
}
// 内部方法,强转类型
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
核心优势:数组的随机访问特性,决定了 get 方法的时间复杂度为 O (1),这是 ArrayList 对比 LinkedList 的最大优势。
4. 删除元素:remove (int index)
public E remove(int index) {
// 索引校验
rangeCheck(index);
modCount++;
// 获取待删除元素
E oldValue = elementData(index);
// 计算需要移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
// 元素前移,覆盖待删除位置
System.arraycopy(elementData, index+1, elementData, index, numMoved);
// 清空最后一个位置的引用(帮助GC),size自减
elementData[--size] = null;
return oldValue;
}
注意:删除后会将最后一个元素的引用置为 null,避免内存泄漏,但数组容量不会自动缩小(如需缩容,可调用 trimToSize() 方法)。
5. JDK8 新增:removeIf (Predicate<? super E> filter)
@Override
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
int removeCount = 0;
// 标记需要删除的元素
final BitSet removeSet = new BitSet(size);
final int expectedModCount = modCount;
final int size = this.size;
// 遍历标记需要删除的元素
for (int i=0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
if (filter.test(element)) {
removeSet.set(i);
removeCount++;
}
}
// 校验并发修改
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// 计算需要保留的元素,前移覆盖
final boolean anyToRemove = removeCount > 0;
if (anyToRemove) {
final int newSize = size - removeCount;
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
// 清空末尾引用
for (int k=newSize; k < size; k++) {
elementData[k] = null;
}
this.size = newSize;
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
return anyToRemove;
}
核心价值:JDK8 新增的批量删除方法,底层通过 BitSet 标记待删除元素,一次性完成数组前移,避免了遍历删除时的并发修改异常,效率远高于手动循环删除。
五、迭代器(Iterator)解析
ArrayList 的迭代器由内部类 Itr 实现,核心是 “快速失败(fail-fast)” 机制:
private class Itr implements Iterator<E> {
int cursor; // 下一个要访问的索引
int lastRet = -1; // 上一个访问的索引
int expectedModCount = modCount; // 迭代器创建时的modCount
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
// 校验modCount,不一致则抛异常
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// 调用ArrayList的remove方法
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
// 更新expectedModCount,避免后续校验失败
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
关键规则:
迭代器创建时记录
modCount,遍历过程中如果集合被修改(如 add/remove),modCount变化,调用next()会立即抛ConcurrentModificationException;只有通过迭代器的
remove()方法删除元素,才会更新expectedModCount,避免异常(这也是遍历删除的正确方式)。
六、JDK8 ArrayList 核心优化与注意事项
1. 核心优化(对比 JDK7)
无参构造延迟初始化:空实例不再占用 10 个元素的数组空间,节省内存;
扩容时数组复制效率优化:底层
System.arraycopy的 native 实现进一步优化;新增函数式方法:
removeIf、forEach等,适配 Lambda 表达式。
2. 注意事项
线程安全:非线程安全,多线程环境下建议使用
CopyOnWriteArrayList或手动加锁;扩容耗时:频繁扩容会降低性能,建议初始化时指定容量;
内存泄漏:删除元素后虽清空引用,但数组容量不变,如需释放内存可调用
trimToSize();快速失败:迭代器遍历过程中,禁止直接修改集合(add/remove),需使用迭代器的
remove()或 JDK8 的removeIf。
七、总结
JDK8 中的 ArrayList 核心是 “动态数组 + 1.5 倍扩容 + 快速失败”,其设计处处体现 “查询优先” 的思想:
基于数组实现,保证 get 方法 O (1) 效率;
扩容机制平衡了 “内存占用” 和 “扩容频率”,1.5 倍扩容是时间 / 空间的最优解;
JDK8 新增的函数式方法(如 removeIf)解决了传统遍历删除的痛点;
快速失败机制避免了并发修改导致的脏数据问题。