在 Java 中,ArrayList 和 LinkedList 都实现了 List 接口,但它们的底层数据结构和工作原理完全不同,这导致了它们在性能特性上的显著差异。选择哪一个取决于你的具体需求。
以下是它们的主要区别:
- 底层数据结构:
-
ArrayList: 基于动态数组。它内部使用一个可调整大小的数组来存储元素。当数组满了需要添加新元素时,它会创建一个更大的新数组,并将旧数组的元素复制过去(这通常涉及 1.5 倍的扩容)。LinkedList: 基于双向链表。每个元素(节点)都包含数据本身以及指向前一个节点和后一个节点的指针(引用)。
- 随机访问性能(按索引获取/设置元素):
-
ArrayList: 非常快 (O(1))。因为它内部是数组,通过索引计算偏移量 (index * elementSize) 可以直接定位到内存地址,一步到位。LinkedList: 非常慢 (O(n))。要访问第i个元素,必须从链表的头部(或尾部,如果i接近尾部且实现了优化)开始遍历,逐个节点查找,直到找到第i个节点。平均需要遍历n/2个节点。
- 插入和删除操作的性能:
-
ArrayList: 通常较慢 (O(n))。
-
-
- 在末尾添加/删除: 如果不需要扩容,很快 (O(1))。如果需要扩容,则因为涉及数组复制而变慢 (O(n))。
- 在开头或中间添加/删除: 很慢 (O(n))。因为需要将插入点之后的所有元素向后移动(添加时)或向前移动(删除时)以腾出空间或填补空缺。插入点越靠前,需要移动的元素越多。
-
-
LinkedList: 通常很快 (O(1))。
-
-
- 在开头或末尾添加/删除: 非常快 (O(1))。只需要修改头/尾节点和相邻节点的指针即可。
- 在中间添加/删除: 定位慢 (O(n)),操作本身快 (O(1))。找到要插入/删除的位置需要遍历链表 (O(n)),但一旦找到目标节点,插入(修改前后节点的指针)或删除(修改相邻节点的指针)操作本身非常快 (O(1))。
-
- 内存占用:
-
ArrayList: 内存开销较小。只存储实际数据和内部数组的容量信息(一个int)。数组是连续的内存块。LinkedList: 内存开销较大。每个元素(节点)都需要额外的内存来存储指向前一个和后一个节点的引用(两个指针)。对于存储大量小对象时,指针的开销可能相当显著。节点在内存中不一定是连续的。
- 遍历性能:
-
ArrayList: 使用普通的for循环(基于索引)遍历最快,因为利用了 O(1) 的随机访问。使用迭代器 (Iterator) 或for-each循环遍历也很快。LinkedList: 绝对不要使用普通的for循环(基于索引)遍历! 因为每次get(i)都是 O(n) 操作,导致整体遍历变成 O(n²),性能极差。必须使用迭代器 (Iterator) 或for-each循环遍历,这样每次移动到下一个节点只需要 O(1) 时间(跟随next指针),整体遍历是 O(n)。使用ListIterator还可以高效地进行双向遍历。
总结表:
|
特性 |
|
|
|
底层结构 |
动态数组 |
双向链表 |
|
随机访问 ( |
极快 (O(1)) |
慢 (O(n)) |
|
头部插入/删除 |
慢 (O(n)) |
极快 (O(1)) |
|
尾部插入/删除 |
快 (O(1)) (不扩容时) / 慢 (O(n)) (扩容时) |
极快 (O(1)) |
|
中间插入/删除 |
慢 (O(n)) |
定位慢 (O(n)) + 操作快 (O(1)) = 平均 O(n) |
|
内存开销 |
较小 (仅数据和数组容量开销) |
较大 (每个节点额外两个指针) |
|
内存连续性 |
连续 |
不连续 |
|
遍历 ( |
非常快 (O(1) per access) |
极差 (O(n²)) ❌ 不要用! |
|
遍历 ( |
快 (O(1) per element) |
快 (O(1) per element) |
如何选择?
- 优先选择
ArrayList: 这是最常用的List实现。在以下场景表现更优:
-
- 你需要频繁地随机访问元素(按索引读取或修改)。
- 你的操作主要在列表末尾进行添加和删除(尤其是在知道大致大小,可以合理初始化容量以减少扩容的情况下)。
- 你需要更小的内存占用(尤其是在存储大量元素时)。
- 大多数情况下,
ArrayList的综合性能更好。
- 考虑选择
LinkedList: 在以下特定场景可能有优势:
-
- 你需要频繁地在列表的开头或中间进行插入和删除操作,并且不太需要按索引随机访问。
- 你需要实现队列 (
Queue) 或 双端队列 (Deque) 的行为(LinkedList实现了这些接口),特别是需要高效的addFirst,addLast,removeFirst,removeLast操作时。 - 内存开销不是主要瓶颈。
简单口诀:
- 随机访问多? ->
ArrayList - 头/中增删多? ->
LinkedList - 不确定? -> 优先
ArrayList(大概率更合适)
记住,如果你需要频繁按索引访问元素,ArrayList 是明确的选择。如果你需要频繁在列表任意位置(尤其是头部)插入删除,且很少随机访问,LinkedList 值得考虑。但在实际应用中,ArrayList 因其良好的综合性能(尤其是随机访问和尾插)而被更广泛地使用。
2384

被折叠的 条评论
为什么被折叠?



