我一直习惯于简单地使用:
List<String> names = new ArrayList<>();
我使用接口作为类型名称来实现可移植性,这样当我像这样提问时,我可以重新组织我的代码。
在什么情况下应该使用 LinkedList
而不是 ArrayList
?反之呢?
我一直习惯于简单地使用:
List<String> names = new ArrayList<>();
我使用接口作为类型名称来实现可移植性,这样当我像这样提问时,我可以重新组织我的代码。
在什么情况下应该使用 LinkedList
而不是 ArrayList
?反之呢?
ArrayList
和ArrayDeque
比LinkedList
更适用于许多使用情况。如果您不确定,只需从ArrayList
开始。
ArrayList
中,访问元素需要常数时间[O(1)],添加元素需要O(n)时间[最坏情况]。在LinkedList
中,插入元素需要O(n)时间,访问也需要O(n)时间,但是LinkedList
使用的内存比ArrayList
多。
LinkedList
和ArrayList
是List
接口的两种不同实现方式。LinkedList
使用双向链表实现它。ArrayList
使用动态调整大小的数组实现它。LinkedList<E>
get(int index)
的时间复杂度为 O(n)(平均需要执行 n/4 步),但当 index = 0
或 index = list.size() - 1
时,时间复杂度为 O(1)(此时也可以使用 getFirst()
和 getLast()
)。LinkedList<E>
的主要优势之一。add(int index, E element)
的时间复杂度为 O(n)(平均需要执行 n/4 步),但当 index = 0
或 index = list.size() - 1
时,时间复杂度为 O(1)(此时也可以使用 addFirst()
和 addLast()
/add()
)。LinkedList<E>
的主要优势之一。remove(int index)
的时间复杂度为 O(n)(平均需要执行 n/4 步),但当 index = 0
或 index = list.size() - 1
时,时间复杂度为 O(1)(此时也可以使用 removeFirst()
和 removeLast()
)。LinkedList<E>
的主要优势之一。Iterator.remove()
的时间复杂度为 O(1)。LinkedList<E>
的主要优势之一。ListIterator.add(E element)
的时间复杂度为 O(1)。LinkedList<E>
的主要优势之一。注意:许多操作平均需要执行 n/4 步,最好情况下(例如 index = 0)需要恒定的步数,最坏情况下(列表中间)需要执行 n/2 步。
对于 ArrayList<E>
get(int index)
是 O(1) 的。这是 ArrayList<E>
的主要优势。add(E element)
在平均情况下是 O(1),但最坏情况下是 O(n),因为数组必须被调整大小并复制。add(int index, E element)
是 O(n) 的(平均需要 n/2 步)。remove(int index)
是 O(n) 的(平均需要 n/2 步)。Iterator.remove()
是 O(n) 的(平均需要 n/2 步)。ListIterator.add(E element)
是 O(n) 的(平均需要 n/2 步)。注意:许多操作在平均情况下需要 n/2 步,最好的情况下需要恒定数量的步骤(列表末尾),最坏的情况下需要 n 步(列表开头)
LinkedList<E>
允许使用迭代器进行常数时间插入或删除,但只允许元素的顺序访问。换句话说,您可以向前或向后遍历列表,但查找列表中的位置需要与列表大小成比例的时间。Javadoc 表示“索引到列表的操作将从开始或结束处遍历列表,以较近者为准”,因此这些方法在平均情况下是 O(n)(n/4 步),但对于 index = 0
是 O(1)。
ArrayList<E>
允许快速随机读取访问,因此您可以在常数时间内获取任何元素。但是,从任何位置添加或删除都需要将所有后续元素移动,以创建空位或填充间隙。此外,如果添加的元素多于底层数组的容量,则会分配一个新数组(大小为原来的 1.5 倍),并将旧数组复制到新数组中,因此在最坏情况下,向 ArrayList
添加是 O(n) 的,但平均是恒定的。
因此,根据您想要执行的操作,应相应地选择实现。迭代两种类型的列表几乎同样便宜。(迭代 ArrayList
在技术上更快,但除非您正在做一些真正性能敏感的事情,否则不必担心——它们都是常数。)
到目前为止,似乎没有人除了普遍认为LinkedList
比ArrayList
占用更多内存之外,对这些列表的内存占用量进行过详细说明,因此我进行了一些数字计算,以演示这两个列表在N个空引用时占用的确切空间。
由于引用在它们相应的系统上是32位或64位(即使为空),因此我包括了32位和64位LinkedLists
和ArrayLists
的4组数据。
注意:ArrayList
行中显示的大小是针对修剪后的列表 - 实际上,在ArrayList
中,支持数组的容量通常大于其当前元素计数。
注意2:(感谢BeeOnRope)由于从JDK6中期开始,默认情况下启用了CompressedOops,因此64位机器的下面的值基本上与它们的32位对应项相匹配,除非您明确关闭它。
结果清楚地显示,特别是在非常高的元素计数下,LinkedList
比ArrayList
占用更多的内存。如果内存是一个因素,请避免使用LinkedLists
。
我使用的公式如下,如果我做错了什么,请告诉我,我会进行修正。'b'对于32位或64位系统,可以是4或8,'n'是元素数量。请注意,进行模运算的原因是因为Java中的所有对象都将占用8字节的空间,无论是否全部使用。
ArrayList:
ArrayList
对象头 + 大小整数 + modCount
整数 + 数组引用 + (数组对象头 + b * n
) + MOD(数组对象, 8) + MOD(ArrayList
对象, 8) == 8 + 4 + 4 + b
+ (12 + b * n
) + MOD(12 + b * n
, 8) + MOD(8 + 4 + 4 + b
+ (12 + b * n
) + MOD(12 + b * n
, 8), 8)
LinkedList:
LinkedList
对象头 + 大小整数 + modCount
整数 + 头引用 + 尾引用 + (节点对象开销 + 前一个元素的引用 + 下一个元素的引用 + 对象引用) * n) + MOD(节点对象, 8) * n + MOD(LinkedList
对象, 8) == 8 + 4 + 4 + 2 * b
+ (8 + 3 * b
) * n + MOD(8 + 3 * b
, 8) * n + MOD(8 + 4 + 4 + 2 * b
+ (8 + 3 * b
) * n + MOD(8 + 3 * b
, 8) * n, 8)
ArrayList
是你所需要的,LinkedList
几乎总是(性能)有问题。
为什么LinkedList
不好:
ArrayList
慢O(n)。ArrayList
相同,它可能仍然会显着较慢。LinkedList
很刺眼,因为它可能是错误的选择。Algorithm ArrayList LinkedList
seek front O(1) O(1)
seek back O(1) O(1)
seek to index O(1) O(N)
insert at front O(N) O(1)
insert at back O(1) O(1)
insert after an item O(N) O(1)
算法:大O符号 (已存档)
ArrayList适用于写一次读多次或者追加,但不适合在中间或开头添加或删除元素。
O(1)
的时间复杂度在中间插入元素。它需要遍历一半的链表以找到插入点。 - Thomas AhleLinkedList
中进行插入操作确实是 O(1)
的,也就是说,对于 LinkedList
来说,ListIterator.add
操作应该是 O(1)
。 - Has QUIT--Anony-Mousse以下是原作者在2021年8月27日的更新:
我写这篇答案时,我的关注点是系统的可用性和鲁棒性。但是随着时间的推移,针对Java的垃圾回收器和集合类的实现方式已经发生了很大变化。目前,对于所谓的“小对象”,G1垃圾回收器通常比CMS提供更好的低延迟。
另外,在性能方面,JDK 15中引入的ZGC收集器可能成为一个不错的选择。与G1相比,它可以处理更大的堆,并且具有更低的停顿时间;并且它还允许您在应用程序运行时动态更改堆大小。
最后,集合类的实现方式也发生了变化。例如,从Java 8开始,在某些情况下,ArrayList的性能可能优于LinkedList,因为它利用了CPU缓存的局部性。此外,Java 9引入了一种名为Compact Strings的新字符串编码格式,它可以显著减少String对象的内存占用量。
这个答案(我在stackoverflow上历史上点赞最多的答案)很可能是错误的(原因在下面的评论中概述)。我想补充说明ArrayList将优化顺序读取内存并最小化缓存行和TLB(翻译注:转换后备缓冲器)未命中等。当数组超过边界时,复制开销可能相对不重要(可以通过高效CPU操作完成)。随着硬件趋势的发展,这个答案也可能变得更糟糕。唯一需要LinkedList的情况可能是某些高度假设的情况,其中有数千个List之一可能会增长到GB大小,但在分配列表时无法做出良好的猜测,并且将它们全部设置为GB大小会使堆积累。如果您发现了这样的问题,那么确实需要重新设计您的解决方案(我不喜欢轻易建议重新设计旧代码,因为我自己维护着大量老代码,但这确实是一个非常好的情况,原始设计已经简单地耗尽了跑道,并且确实需要被扔掉)。我仍然会把我的几十年前的愚见留给你阅读。简单、逻辑和非常错误。
LinkedList
比一个简单的引用数组总是分配五倍的内存,因此,即使在内存未被回收时,暂时需要2.5倍空间的ArrayList
仍然消耗更少的内存。由于大型数组分配绕过了伊甸园空间,它们不会对垃圾回收行为产生任何影响,除非真的没有足够的内存,在这种情况下,LinkedList
会更早地出现问题... - HolgerLinkedList的作者Joshua Bloch曾说:
有人真正使用LinkedList吗?我写了它,但我从不使用它。
链接:https://twitter.com/joshbloch/status/583813919019573248
抱歉我的答案可能不像其他答案那么详细,但我觉得这是最自解释的答案,虽然没有透露更多信息。
是的,我知道这是一个古老的问题,但我会提供我的意见:
LinkedList在大多数情况下从性能上来说都不是一个好的选择。有一些非常特殊的算法需要使用LinkedList,但这些情况非常罕见,而且算法通常需要利用LinkedList相对快速地插入和删除列表中间的元素,一旦你使用ListIterator导航到那里。
有一种常见的用例,LinkedList优于ArrayList:队列。然而,如果您的目标是性能,除了LinkedList之外,您还应该考虑使用ArrayBlockingQueue(如果您可以预先确定队列大小的上限,并且可以承受一次性分配所有内存),或者使用CircularArrayList实现。(是的,它来自2001年,所以您需要泛型化它,但我刚才在最近的JVM中得到了与文章中引用的可比性能比率)
ArrayDeque
。http://docs.oracle.com/javase/6/docs/api/java/util/ArrayDeque.html - Thomas AhleArrayDeque
比 LinkedList
慢,除非所有操作都在同一端。当用作堆栈时可以,但不适合用作队列。 - Jeremy ListArrayDeque
比 Stack
更快,当用作队列时,比 LinkedList
更快。 - akhil_mittalLinkedList
速度快,但访问特定元素较慢。对于访问特定元素,ArrayList
速度快,但在两端添加元素可能较慢,尤其是在中间删除元素时更慢。
Array vs ArrayList vs LinkedList vs Vector提供了更深入的解释,Linked List也是如此。LinkedList
在仅对第一个和最后一个位置进行添加/移除时速度很快-此时复杂度为O(1),但是在中间添加则仍为O(n),因为我们需要遍历大约n/2个LinkedList
元素。 - Dmitriy Fialkovskiy正确或错误:请在本地执行测试,自己决定!
在LinkedList
中,编辑/删除比ArrayList
更快。
ArrayList
由Array
支持,需要将其大小加倍,在大容量应用程序中效果更差。
下面是每个操作的单元测试结果。计时以纳秒为单位。
Operation ArrayList LinkedList
AddAll (Insert) 101,16719 2623,29291
Add (Insert-Sequentially) 152,46840 966,62216
Add (insert-randomly) 36527 29193
remove (Delete) 20,56,9095 20,45,4904
contains (Search) 186,15,704 189,64,981
这是代码:
import org.junit.Assert;
import org.junit.Test;
import java.util.*;
public class ArrayListVsLinkedList {
private static final int MAX = 500000;
String[] strings = maxArray();
////////////// ADD ALL ////////////////////////////////////////
@Test
public void arrayListAddAll() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
watch.start();
arrayList.addAll(stringList);
watch.totalTime("Array List addAll() = ");//101,16719 Nanoseconds
}
@Test
public void linkedListAddAll() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
watch.start();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(stringList);
watch.totalTime("Linked List addAll() = "); //2623,29291 Nanoseconds
}
//Note: ArrayList is 26 time faster here than LinkedList for addAll()
///////////////// INSERT /////////////////////////////////////////////
@Test
public void arrayListAdd() {
Watch watch = new Watch();
List<String> arrayList = new ArrayList<String>(MAX);
watch.start();
for (String string : strings)
arrayList.add(string);
watch.totalTime("Array List add() = ");//152,46840 Nanoseconds
}
@Test
public void linkedListAdd() {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
watch.start();
for (String string : strings)
linkedList.add(string);
watch.totalTime("Linked List add() = "); //966,62216 Nanoseconds
}
//Note: ArrayList is 9 times faster than LinkedList for add sequentially
/////////////////// INSERT IN BETWEEN ///////////////////////////////////////
@Test
public void arrayListInsertOne() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX + MAX / 10);
arrayList.addAll(stringList);
String insertString0 = getString(true, MAX / 2 + 10);
String insertString1 = getString(true, MAX / 2 + 20);
String insertString2 = getString(true, MAX / 2 + 30);
String insertString3 = getString(true, MAX / 2 + 40);
watch.start();
arrayList.add(insertString0);
arrayList.add(insertString1);
arrayList.add(insertString2);
arrayList.add(insertString3);
watch.totalTime("Array List add() = ");//36527
}
@Test
public void linkedListInsertOne() {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(stringList);
String insertString0 = getString(true, MAX / 2 + 10);
String insertString1 = getString(true, MAX / 2 + 20);
String insertString2 = getString(true, MAX / 2 + 30);
String insertString3 = getString(true, MAX / 2 + 40);
watch.start();
linkedList.add(insertString0);
linkedList.add(insertString1);
linkedList.add(insertString2);
linkedList.add(insertString3);
watch.totalTime("Linked List add = ");//29193
}
//Note: LinkedList is 3000 nanosecond faster than ArrayList for insert randomly.
////////////////// DELETE //////////////////////////////////////////////////////
@Test
public void arrayListRemove() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
arrayList.addAll(stringList);
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
arrayList.remove(searchString0);
arrayList.remove(searchString1);
watch.totalTime("Array List remove() = ");//20,56,9095 Nanoseconds
}
@Test
public void linkedListRemove() throws Exception {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(Arrays.asList(strings));
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
linkedList.remove(searchString0);
linkedList.remove(searchString1);
watch.totalTime("Linked List remove = ");//20,45,4904 Nanoseconds
}
//Note: LinkedList is 10 millisecond faster than ArrayList while removing item.
///////////////////// SEARCH ///////////////////////////////////////////
@Test
public void arrayListSearch() throws Exception {
Watch watch = new Watch();
List<String> stringList = Arrays.asList(strings);
List<String> arrayList = new ArrayList<String>(MAX);
arrayList.addAll(stringList);
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
arrayList.contains(searchString0);
arrayList.contains(searchString1);
watch.totalTime("Array List addAll() time = ");//186,15,704
}
@Test
public void linkedListSearch() throws Exception {
Watch watch = new Watch();
List<String> linkedList = new LinkedList<String>();
linkedList.addAll(Arrays.asList(strings));
String searchString0 = getString(true, MAX / 2 + 10);
String searchString1 = getString(true, MAX / 2 + 20);
watch.start();
linkedList.contains(searchString0);
linkedList.contains(searchString1);
watch.totalTime("Linked List addAll() time = ");//189,64,981
}
//Note: Linked List is 500 Milliseconds faster than ArrayList
class Watch {
private long startTime;
private long endTime;
public void start() {
startTime = System.nanoTime();
}
private void stop() {
endTime = System.nanoTime();
}
public void totalTime(String s) {
stop();
System.out.println(s + (endTime - startTime));
}
}
private String[] maxArray() {
String[] strings = new String[MAX];
Boolean result = Boolean.TRUE;
for (int i = 0; i < MAX; i++) {
strings[i] = getString(result, i);
result = !result;
}
return strings;
}
private String getString(Boolean result, int i) {
return String.valueOf(result) + i + String.valueOf(!result);
}
}
LinkedList
的内存开销更大,因为对于每个元素,都有一个具有五个字段的节点对象。在许多系统上,这会产生20字节的开销。ArrayList
每个元素的平均内存开销是一个半字,即6字节,在最坏情况下是8字节。 - LiiremoveIf(element -> condition)
,这对于ArrayList
而言可以比通过迭代器循环和删除更快,因为不需要为每个元素移动整个剩余部分。这是否比LinkedList
表现更好或更差取决于特定场景,因为LinkedList
在理论上是O(1),但仅删除一个节点就需要多次内存访问,这很容易超过删除大量元素时所需的数量。 - HolgerArrayList
本质上是一个数组。 LinkedList
是作为双向链表实现的。
get
很清楚。对于ArrayList
,它允许使用索引进行随机访问,因此为O(1)。对于LinkedList
,由于需要先找到索引,因此为O(n)。注意:有不同版本的add
和remove
.
LinkedList
在添加和删除方面更快,但在获取方面更慢。简而言之,如果:
应该首选LinkedList
。
=== ArrayList ===
=== LinkedList ===
add(E e)
add(int index,E element)
以下是来自 programcreek.com 的图片(add
和 remove
属于第一类,即在列表末尾添加元素并删除列表中指定位置的元素):
std::vector
(类似于Java的ArrayList
)和std::list
(类似于Java的LinkedList
)。 - kevinarpe