一、ArrayList的本质:动态数组
核心定义:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 底层数据结构
transient Object[] elementData;
// 实际元素数量
private int size;
}
关键特性:
-
动态扩容:初始容量10,按1.5倍因子增长
-
快速随机访问:实现
RandomAccess
接口 -
非线程安全:多线程环境需外部同步
-
空间优化:自动缩容(需显式调用)
二、底层实现原理揭秘
1. 扩容机制(核心源码解析)
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 新容量 = 旧容量 * 1.5
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 关键:创建新数组并复制数据
elementData = Arrays.copyOf(elementData, newCapacity);
}
2. 时间复杂度分析
操作 | 时间复杂度 | 说明 |
---|---|---|
get(int index) | O(1) | 直接数组下标访问 |
add(E element) | 均摊O(1) | 尾部插入,偶尔触发扩容 |
add(int index) | O(n) | 需要移动后续所有元素 |
remove(int index) | O(n) | 需要移动后续元素 |
contains(Object) | O(n) | 遍历查找 |
三、性能陷阱与优化策略
1. 初始容量优化
// 错误:默认初始容量10
List<User> users = new ArrayList<>();
// 正确:预分配足够容量(减少扩容次数)
List<User> users = new ArrayList<>(10000);
2. 批量操作优化
// 错误:逐个添加(触发多次扩容)
for (int i = 0; i < 100000; i++) {
list.add(i);
}
// 正确:批量添加(减少扩容次数)
list.addAll(IntStream.range(0, 100000).boxed().collect(Collectors.toList()));
3. 遍历方式性能对比
// 最佳:for循环(随机访问)
for (int i = 0; i < list.size(); i++) {
process(list.get(i));
}
// 次佳:迭代器
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
process(it.next());
}
// 最差:for-each(语法糖,实际使用迭代器)
for (String s : list) {
process(s);
}
四、线程安全问题与解决方案
1. 典型并发问题
List<Integer> unsafeList = new ArrayList<>();
// 多线程并发添加元素
IntStream.range(0, 10).parallel().forEach(unsafeList::add);
// 可能结果:
// 1. 元素丢失
// 2. size > capacity (ArrayIndexOutOfBounds)
// 3. size 不准确
2. 线程安全解决方案
// 方案1:Collections.synchronizedList(全方法同步)
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 方案2:CopyOnWriteArrayList(读多写少场景)
List<String> cowList = new CopyOnWriteArrayList<>();
// 方案3:Vector(遗留方案,不推荐)
List<String> vector = new Vector<>();
五、ArrayList vs LinkedList
特性 | ArrayList | LinkedList |
---|---|---|
底层结构 | 动态数组 | 双向链表 |
随机访问 | O(1) | O(n) |
头部插入 | O(n) | O(1) |
内存占用 | 紧凑(连续内存) | 分散(节点开销) |
缓存友好性 | 高 | 低 |
适用场景 | 读多写少 | 写多读少 |