Java List实现类包括ArrayList(动态数组,查询快)、LinkedList(双向链表增删快)、Vector(同步低效)和CopyOnWriteArrayList(写时复制,读多写少高并发),适用于不同线程与操作场景。
一、List 接口核心特性
- 有序性:元素按插入顺序存储,可通过索引(
index)访问。 - 可重复性:允许存储重复元素。
- 支持 null:可以包含
null元素。 - 动态扩容:大多数实现类自动处理容量扩展。
二、List 主要实现类对比
| 实现类 | 底层数据结构 | 线程安全 | 随机访问效率 | 插入/删除效率 | 适用场景 |
|---|---|---|---|---|---|
| ArrayList | 动态数组 | 非线程安全 | O(1) | O(n) | 频繁查询、少量修改 |
| LinkedList | 双向链表 | 非线程安全 | O(n) | O(1) | 频繁插入删除、无需随机访问 |
| Vector | 动态数组 | 线程安全 | O(1) | O(n) | 遗留代码、需同步的动态数组 |
| CopyOnWriteArrayList | 动态数组(写时复制) | 线程安全 | O(1)(读) | O(n)(写) | 高并发读、极少写操作的场景 |
三、各实现类详解
1. ArrayList
-
底层数据结构:基于
Object[] elementData的动态数组。 -
扩容机制:
-
初始容量:默认 10(无参构造函数初始化时为空数组,首次添加元素时扩容至10)。
-
扩容规则:当添加元素导致
size + 1 > elementData.length时触发扩容。 -
新容量计算:
newCapacity = oldCapacity + max(所需最小增量, oldCapacity / 2)(即扩容至1.5倍)。 -
示例代码:
// JDK 17 源码片段(简化) private Object[] grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = ArraysSupport.newLength( oldCapacity, minCapacity - oldCapacity, // 最小增量 oldCapacity >> 1 // 默认扩容1.5倍 ); return Arrays.copyOf(elementData, newCapacity); }
-
-
线程安全:非线程安全,需通过
Collections.synchronizedList()包装或使用CopyOnWriteArrayList。 -
优点:
- 随机访问速度快(直接通过索引定位)。
- 内存连续,缓存友好。
-
缺点:
- 插入/删除元素需移动后续元素,效率低。
- 频繁扩容导致内存复制开销。
2. LinkedList
-
底层数据结构:基于双向链表(
Node节点)。private static class Node<E> { E item; Node<E> prev; Node<E> next; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.prev = prev; this.next = next; } } -
操作机制:
- 插入/删除:直接调整相邻节点的引用(时间复杂度 O(1))。
- 随机访问:需从头或尾遍历链表(时间复杂度 O(n))。
-
线程安全:非线程安全,需外部同步。
-
优点:
- 头尾插入删除高效。
- 无需预先分配内存,动态扩展灵活。
-
缺点:
- 随机访问性能差。
- 内存占用较高(每个元素需存储前后节点引用)。
3. Vector
-
底层数据结构:与
ArrayList类似,基于动态数组。 -
线程安全:通过
synchronized关键字实现方法级同步。public synchronized boolean add(E e) { modCount++; add(e, elementData, elementCount); return true; } -
扩容机制:默认扩容至原容量的 2 倍(可通过构造函数指定增量)。
-
优点:线程安全。
-
缺点:
- 同步锁导致高并发下性能差。
- 已逐渐被
CopyOnWriteArrayList或同步包装类取代。
4. CopyOnWriteArrayList
-
底层数据结构:基于
volatile Object[] array的动态数组。 -
写时复制(COW)机制:
-
写操作(如
add、set)时,加锁并复制原数组到新数组,修改后替换原数组引用。public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); // 原子替换数组引用 return true; } finally { lock.unlock(); } } -
读操作:直接访问当前数组(无锁,读性能极高)。
-
-
线程安全:通过写时复制和
ReentrantLock保证线程安全。 -
优点:
- 读操作完全无锁,适合读多写少场景。
- 迭代器不会抛出
ConcurrentModificationException。
-
缺点:
- 写操作内存开销大(需复制整个数组)。
- 数据一致性弱(迭代器访问的是旧数组快照)。
四、扩容机制对比
| 实现类 | 初始容量 | 扩容规则 | 扩容触发条件 |
|---|---|---|---|
| ArrayList | 10 | 扩容至1.5倍 | size + 1 > elementData.length |
| Vector | 10 | 扩容至2倍(可指定增量) | size + 1 > elementData.length |
| CopyOnWriteArrayList | 0 | 每次写操作复制数组并扩展1 | 每次添加元素时触发 |
五、线程安全方案对比
| 方案 | 实现方式 | 适用场景 | 性能影响 |
|---|---|---|---|
| Vector | 方法级同步(synchronized) | 低并发简单同步需求 | 高锁竞争,性能差 |
| Collections.synchronizedList | 包装类加锁(对象锁) | 简单同步需求 | 锁粒度粗,性能中等 |
| CopyOnWriteArrayList | 写时复制 + ReentrantLock | 读多写极少的高并发场景 | 写操作开销大,读无锁 |
六、选择建议
-
单线程环境:
- 频繁查询 →
ArrayList。 - 频繁插入删除 →
LinkedList。
- 频繁查询 →
-
多线程环境:
- 读多写少 →
CopyOnWriteArrayList。 - 写操作频繁 →
Collections.synchronizedList(new ArrayList<>())或并发队列替代。
- 读多写少 →
-
遗留系统维护 →
Vector(不推荐新项目使用)。
七、示例代码:ArrayList 扩容流程
ArrayList<Integer> list = new ArrayList<>(3); // 初始容量3
list.add(1); // 容量足够,无需扩容
list.add(2);
list.add(3);
list.add(4); // 触发扩容:newCapacity = 3 + 3/2 = 4.5 → 取整为4
// 新数组容量为4,旧数组元素复制到新数组
八、总结
- ArrayList:适合随机访问,内存高效,但插入删除性能差。
- LinkedList:适合频繁插入删除,但内存占用高。
- Vector:线程安全但性能低,已过时。
- CopyOnWriteArrayList:高并发读场景首选,但写操作代价高。