ArrayList 与 LinkedList 的核心区别
在 Java 中,ArrayList
和LinkedList
是两种常用的列表实现,它们在底层结构、性能特性和适用场景上有显著差异。以下从多个维度详细对比:
1. 底层数据结构
对比项 | ArrayList | LinkedList |
---|---|---|
数据结构 | 动态数组(Object[]) | 双向链表(每个节点包含前驱和后继指针) |
存储方式 | 连续内存空间存储元素 | 非连续内存,通过指针关联元素 |
内存占用 | 需预留容量(默认 10),可能浪费空间 | 每个节点需额外存储指针,空间开销大 |
2. 性能特性对比
操作类型 | ArrayList | LinkedList |
---|---|---|
随机访问(get/set) | O(1)(直接通过索引访问) | O(n)(需从头或尾遍历链表) |
尾部插入(add) | 平均O(1)(扩容时 O (n)) | O(1)(直接插入到尾部节点) |
头部插入(addFirst) | O (n)(需移动所有元素) | O(1)(修改头指针) |
中间插入(add (index)) | O (n/2)(平均移动一半元素) | O (n/2)(平均需遍历到中间节点) |
删除操作(remove) | O (n/2)(移动元素填补空缺) | O (n/2)(需先定位节点) |
3. 扩容机制
-
ArrayList:
初始容量为 10,扩容时新容量为原容量的1.5 倍(oldCapacity + (oldCapacity >> 1)
)。
扩容需复制数组,开销较大。 -
LinkedList:
无需预分配容量,通过指针动态添加节点,无扩容开销。
4. 线程安全性
- 两者均非线程安全。
若需线程安全,可通过以下方式:java
// ArrayList线程安全包装 List<String> safeArrayList = Collections.synchronizedList(new ArrayList<>()); // LinkedList线程安全包装 List<String> safeLinkedList = Collections.synchronizedList(new LinkedList<>());
或使用CopyOnWriteArrayList
(适用于读多写少场景)。
5. 适用场景
场景 | 推荐选择 | 原因 |
---|---|---|
随机访问频繁 | ArrayList | O (1) 时间复杂度,数组天然支持随机访问。 |
插入 / 删除频繁(尤其是尾部) | ArrayList | 尾部插入平均 O (1),优于 LinkedList 的指针操作开销。 |
插入 / 删除频繁(尤其是头部或中间) | LinkedList | 无需移动元素,仅需修改指针。 |
内存敏感 | ArrayList | 无额外指针开销,空间利用率高(需合理控制初始容量)。 |
需双端队列操作 | LinkedList | 实现Deque 接口,支持高效的头 / 尾操作(如addFirst() 、pollLast() )。 |
6. 代码示例对比
java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ListComparison {
public static void main(String[] args) {
// ArrayList示例
List<String> arrayList = new ArrayList<>();
arrayList.add("A"); // 尾部插入快
arrayList.add(0, "B"); // 头部插入需移动元素,慢
String element = arrayList.get(1); // 随机访问快
// LinkedList示例
LinkedList<String> linkedList = new LinkedList<>();
linkedList.addFirst("C"); // 头部插入快
linkedList.addLast("D"); // 尾部插入快
linkedList.removeFirst(); // 头部删除快
}
}
7. 总结对比表
特性 | ArrayList | LinkedList |
---|---|---|
底层结构 | 动态数组 | 双向链表 |
随机访问 | 极快(O (1)) | 慢(O (n)) |
头部插入 / 删除 | 慢(O (n)) | 快(O (1)) |
尾部插入 / 删除 | 快(O (1),扩容时 O (n)) | 快(O (1)) |
中间插入 / 删除 | 慢(O (n)) | 慢(O (n),但指针操作开销小) |
内存占用 | 数组预分配,可能浪费空间 | 每个节点需额外存储两个指针 |
实现接口 | List | List 、Deque |
适用场景 | 随机访问为主,尾部增删较多 | 频繁头部 / 中间增删,双端队列操作 |
8. 面试常见问题
-
为什么 ArrayList 随机访问快?
答:数组在内存中连续存储,通过索引可直接计算内存地址(偏移量)。 -
为什么 LinkedList 插入删除快?
答:插入 / 删除仅需修改指针,无需移动元素(但需先定位节点,时间复杂度仍为 O (n))。 -
ArrayList 的扩容机制是什么?
答:初始容量 10,扩容时新容量为原容量的 1.5 倍,通过Arrays.copyOf()
复制数组。 -
如何选择 ArrayList 和 LinkedList?
答:根据访问和修改操作的频率选择。若随机访问多,选 ArrayList;若频繁在头部 / 中间插入删除,选 LinkedList。
总结
- ArrayList适合随机访问频繁、尾部增删较多的场景,牺牲部分插入性能换取高效的随机访问。
- LinkedList适合频繁头部 / 中间增删、需双端队列操作的场景,牺牲随机访问性能换取灵活的插入删除。
在实际开发中,需根据具体业务场景权衡选择,避免因误用导致性能问题。