Java集合 - ArrayList底层源码解析
下面开始对 Java 中 ArrayList
的深度源码分析,基于 JDK 8
的实现(后续版本略有差异,但核心逻辑一致)。我们将从 类结构、扩容机制、核心方法实现、性能优化、线程安全问题 等角度进行详细解析
一、类结构与核心字段
1. 类继承关系
public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
AbstractList
:提供了 List 接口的部分默认实现(如get(int index)
、size()
等)RandomAccess
:标记接口,表示支持随机访问(通过索引访问元素,O(1)
时间复杂度)Cloneable
:支持克隆Serializable
:支持序列化
2. 核心字段
// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组常量(用于无参构造)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 空数组常量(用于指定容量为 0 的构造)
private static final Object[] EMPTY_ELEMENTDATA = {};
// 存储元素的数组
transient Object[] elementData;
// 当前元素个数
private int size;
// 数组最大容量限制(防止溢出)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
关键点
elementData
:实际存储数据的数组,初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
(空数组),首次添加元素时扩容到默认容量 10size
:记录当前集合中实际元素的数量,与elementData.length
不同。transient
:elementData
被标记为transient
,但ArrayList
自定义了writeObject
和readObject
方法,实现序列化逻辑MAX_ARRAY_SIZE
:限制数组最大容量为Integer.MAX_VALUE - 8
,避免 JVM 内存分配异常
.
二、构造方法详解
1. 无参构造函数
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 初始化为空数组
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
- 首次添加元素时,会扩容到默认容量 10
2. 带初始容量的构造函数
public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {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) {if (elementData.getClass() != Object[].class)elementData = Arrays.copyOf(elementData, size, Object[].class);} else {this.elementData = EMPTY_ELEMENTDATA;}
}
- 将集合转换为数组并赋值给
elementData
- 如果集合返回的数组类型不是
Object[]
,会强制转换(避免类型问题)
.
三、核心方法源码分析
1. 添加元素:add(E e)
public boolean add(E e) {modCount++; // 修改次数 +1(用于迭代器 fail-fast)add(e, elementData, size);return true;
}private void add(E e, Object[] elementData, int s) {if (s == elementData.length)elementData = grow(); // 扩容elementData[s] = e;size = s + 1;
}
- 关键步骤:
- 检查是否需要扩容
(s == elementData.length)
- 调用
grow()
方法扩容 - 将元素赋值到
elementData[s]
- 更新
size
- 检查是否需要扩容
2. 扩容机制:grow()
private Object[] grow() {return grow(size + 1);
}private Object[] grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5 倍扩容if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);return elementData = Arrays.copyOf(elementData, newCapacity);
}
- 扩容规则:
- 默认扩容:
newCapacity = oldCapacity + (oldCapacity >> 1)
(即 1.5 倍) - 特殊情况:如果扩容后仍不足(如一次性添加大量元素),直接设置为
minCapacity
- 最大容量限制:超过
MAX_ARRAY_SIZE
时调用hugeCapacity()
处理
- 默认扩容:
hugeCapacity(int minCapacity)
private static int hugeCapacity(int minCapacity) {if (minCapacity < 0)throw new OutOfMemoryError();return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
- 如果
minCapacity
超过MAX_ARRAY_SIZE
,则返回Integer.MAX_VALUE
,否则返回MAX_ARRAY_SIZE
3. 删除元素:remove(int index)
public E remove(int index) {Objects.checkIndex(index, size); // 检查索引合法性final Object[] es = elementData;@SuppressWarnings("unchecked") E oldValue = (E) es[index];int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(es, index+1, es, index, numMoved); // 左移元素es[--size] = null; // 帮助 GCreturn oldValue;
}
- 关键步骤:
- 检查索引合法性
- 获取目标元素
- 将右半部分元素左移(时间复杂度 O(n))
- 将最后一个元素置为 null,帮助垃圾回收
- 更新 size
4. 获取元素:get(int index)
public E get(int index) {Objects.checkIndex(index, size);return (E) elementData[index];
}
- 特点:直接通过索引访问数组元素,时间复杂度
O(1)
5. 修改元素:set(int index, E element)
public E set(int index, E element) {Objects.checkIndex(index, size);E oldValue = (E) elementData[index];elementData[index] = element;return oldValue;
}
- 特点:直接修改数组中指定位置的元素,时间复杂度
O(1)
.
四、性能优化技巧
1. 预估容量,避免频繁扩容
使用 ArrayList(int initialCapacity)
构造函数,提前指定容量。如果容量不足,频繁扩容会导致性能下降(每次扩容需复制数组)
2. 手动扩容:ensureCapacity(int minCapacity)
public void ensureCapacity(int minCapacity) {int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)? 0 : DEFAULT_CAPACITY;if (minCapacity > minExpand) {modCount++;grow(minCapacity);}
}
- 在添加大量元素前,调用此方法预扩容,减少中间扩容次数
3. 清空集合:clear()
public void clear() {modCount++;for (int i = 0; i < size; i++)elementData[i] = null; // 帮助 GCsize = 0;
}
- 注意:
clear()
不会释放数组空间,只是将size
置为 0 并将元素设为null
.
五、线程安全问题
1. 线程不安全
ArrayList
不是线程安全的,多线程环境下可能引发数据不一致或 ConcurrentModificationException
2. 解决方案
- 使用
Collections.synchronizedList
:
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
- 使用
CopyOnWriteArrayList
(适合读多写少场景):
List<String> cowList = new CopyOnWriteArrayList<>();
.
六、Fail-Fast 机制
1. 原理
ArrayList
通过modCount
变量记录集合的修改次数- 迭代器遍历时会检查
modCount
是否与创建迭代器时的值一致。如果不一致,抛出ConcurrentModificationException
2. 代码示例
public Iterator<E> iterator() {return new Itr();
}private class Itr implements Iterator<E> {int expectedModCount = modCount;public boolean hasNext() {checkForComodification();return cursor != size;}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
}
.
七、性能对比与使用建议
使用建议
- 高频随机访问:优先使用
ArrayList
- 频繁中间插入/删除:使用
LinkedList
- 线程安全:使用
Collections.synchronizedList
或CopyOnWriteArrayList
八、其他实用方法
九、总结
十、扩展学习建议
- 源码调试:使用 IDE(如 IntelliJ IDEA)逐步调试 ArrayList 的
add
、remove
等方法,观察elementData
和size
的变化 - 版本差异:对比 JDK 8 和 JDK 17 的
ArrayList
源码,观察扩容策略、subList
实现等细节变化 - 进阶主题:
ArrayList
与Vector
的区别(线程安全 vs 性能)subList
返回的视图对原集合的影响ArrayList
在 JVM 内存模型中的表现(数组的连续性 vs 链表的离散性)