在 Java 中,ArrayList 是一个常用的动态数组实现类,它位于 java.util 包下。下面详细讲解其底层原理:
1. 基本结构
- 基于数组实现:
ArrayList
的底层是一个动态扩容的对象数组(transient Object[] elementData
),这意味着它可以像普通数组一样通过索引快速访问元素。 - 继承关系:继承自
AbstractList
并实现了List
接口,因此支持List
的所有操作。
2. 初始化与容量
- 默认容量:当使用无参构造函数
new ArrayList()
时,初始容量为 0(JDK 8 及以后),首次添加元素时扩容为 10。 - 指定容量:使用
new ArrayList(int initialCapacity)
可以指定初始容量,避免频繁扩容。 - 空参构造的延迟初始化:
// 无参构造:初始化为空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 首次添加元素时,容量扩展为 DEFAULT_CAPACITY(10)
3. 动态扩容机制
当元素数量超过数组容量时,ArrayList
会自动扩容:
- 计算新容量:默认扩容为原容量的 1.5 倍(
oldCapacity + (oldCapacity >> 1)
)。 - 创建新数组:使用
Arrays.copyOf()
方法将原数组元素复制到新数组。 - 替换引用:将内部数组引用指向新数组。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; // 特殊情况:初始容量为0时
elementData = Arrays.copyOf(elementData, newCapacity);
}
4. 元素操作
- 添加元素(
add(E e)
):- 尾部添加:平均时间复杂度为 O(1),可能触发扩容(概率低)。
- 指定位置插入(
add(int index, E element)
):需要移动后续元素,时间复杂度为 O(n)。
- 删除元素(
remove(int index)
):- 需要移动后续元素,时间复杂度为 O(n)。
- 获取元素(
get(int index)
):- 直接通过数组索引访问,时间复杂度为 O(1)。
- 修改元素(
set(int index, E element)
):- 直接通过索引替换值,时间复杂度为 O(1)。
5. 自动装箱与拆箱
- 当存储基本数据类型(如
int
、double
)时,ArrayList
会自动进行装箱(Integer
、Double
)和拆箱操作,可能影响性能。
6. 线程安全性
- 非线程安全:
ArrayList
不支持并发访问,多线程环境下可能出现数据不一致问题。 - 替代方案:
- 使用
Vector
(线程安全,但性能较低)。 - 使用
Collections.synchronizedList(new ArrayList<>())
。 - 使用
CopyOnWriteArrayList
(写时复制,适合读多写少场景)。
- 使用
7. 迭代器与 fail-fast 机制
- 迭代器(
Iterator
):通过iterator()
方法获取,支持元素遍历和删除。 - fail-fast 机制:
- 当迭代过程中检测到数组结构被修改(如添加、删除元素),会抛出
ConcurrentModificationException
。 - 通过内部维护的
modCount
变量实现,每次结构修改时递增。
- 当迭代过程中检测到数组结构被修改(如添加、删除元素),会抛出
8. 序列化与 transient 关键字
elementData
被声明为transient
,表示不自动参与序列化。- 原因:数组可能存在未使用的空位,序列化会浪费空间。
- 自定义序列化逻辑:通过
writeObject()
和readObject()
方法只序列化实际存储的元素。
9. 优缺点总结
- 优点:
- 随机访问效率高(O (1))。
- 支持动态扩容。
- 缺点:
- 插入和删除操作效率低(O (n))。
- 扩容会导致数组复制,性能损耗。
10. 适用场景
- 频繁随机访问元素。
- 很少进行插入和删除操作。
- 存储元素数量不确定,但变化不大。
总结
ArrayList
通过动态数组和扩容机制实现了可变长度的列表,适合随机访问场景。理解其底层原理有助于合理使用该类,并在性能敏感的场景下做出优化选择。