什么时候在Java中使用LinkedList而不是ArrayList?

我一直是一个简单地使用:

List<String> names = new ArrayList<>();

我使用接口作为可移植性的类型名称,这样当我问这样的问题时,我可以返工我的代码。

什么时候应该使用#0而不是#1,反之亦然?

1324368 次浏览

这是一个效率问题。LinkedList在添加和删除元素时速度很快,但访问特定元素时速度很慢。ArrayList在访问特定元素时速度很快,但添加到任何一端都可能很慢,删除中间的元素尤其慢。

数组vs数组列表vs LinkedList vs向量更深入,就像链表.

ArrayList是随机访问的,而LinkedList扩展和删除元素非常便宜。在大多数情况下,ArrayList很好。

除非您创建了大型列表并测量了瓶颈,否则您可能永远不需要担心差异。

这取决于您将在列表中执行哪些操作。

ArrayList访问索引值更快。插入或删除对象时更糟糕。

要了解更多信息,请阅读任何讨论数组和链表之间区别的文章。

除了上面的其他好的参数,您应该注意到ArrayList实现了RandomAccess接口,而LinkedList实现了Queue

因此,他们以某种方式解决略有不同的问题,效率和行为不同(参见他们的方法列表)。

总结 ArrayListArrayDeque很多更多的使用案例中优于 LinkedList。如果你不确定ーー就从 ArrayList开始。


在 TLDR 中,在 ArrayList中访问一个元素需要常量时间[ O (1)] ,添加一个元素需要 O (n)时间[最坏的情况]。在 LinkedList中,插入一个元素需要 O (n)时间,访问也需要 O (n)时间,但是 LinkedListArrayList使用更多的内存。

LinkedListArrayListList接口的两种不同实现。LinkedList使用双链表实现它。ArrayList使用一个动态调整大小的数组来实现它。

与标准的链表和数组操作一样,各种方法将有不同的算法运行时。

对于 LinkedList<E>

  • get(int index)O (n)(平均 N/4步) ,而 O (1)index = 0index = list.size() - 1(在这种情况下,也可以使用 getFirst()getLast())。的主要好处之一 LinkedList<E>
  • add(int index, E element)O (n)(平均有 N/4步) ,而 O (1)index = 0index = list.size() - 1(在这种情况下,也可以使用 addFirst()addLast()/add())。index = 00 LinkedList<E>
  • remove(int index)O (n)(平均 N/4步) ,而 O (1)index = 0index = list.size() - 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 = 0O (1)

另一方面,ArrayList<E>允许快速随机读取访问,因此可以在恒定时间内获取任何元素。但是增加或删除从任何地方,但结束需要移动所有后者的元素,无论是作出一个开放或填补空白。另外,如果你添加的元素超过了底层数组的容量,一个新的数组(1.5倍的大小)将被分配,旧的数组将被复制到新的数组中,所以向 ArrayList中添加的元素在最坏的情况下是 O (n),但是平均值是常数。

因此,根据您打算执行的操作,您应该相应地选择实现。对这两种 List 进行迭代实际上同样便宜。(在 ArrayList上迭代在技术上更快,但是除非您正在执行一些真正与性能相关的操作,否则不必担心这个问题——它们都是常量。)

使用 LinkedList的主要好处出现在重用现有迭代器插入和删除元素时。然后可以在 O (1)中通过仅在本地更改列表来完成这些操作。在数组列表中,数组的其余部分需要是 感动(即复制)。另一方面,在 LinkedList中寻找意味着在最坏的情况下遵循 O (n)(N/2步骤)中的链接,而在 ArrayList中,所需的位置可以通过数学计算并在 O (1)中访问。

使用 LinkedList的另一个好处是,当您从列表的头部添加或删除时,因为这些操作是 O (1),而对于 ArrayListO (n)。请注意,ArrayDeque可能是一个很好的替代 LinkedList增加和删除从头,但它不是一个 List

另外,如果您有大型列表,请记住内存使用也是不同的。LinkedList的每个元素都有更多的开销,因为指向下一个元素和前一个元素的指针也存储在其中。ArrayLists没有这个开销。但是,无论实际上是否添加了元素,ArrayLists占用的内存与为容量分配的内存一样多。

ArrayList的默认初始容量非常小(Java 1.4-1.8为10)。但是由于底层实现是一个数组,因此如果添加大量元素,则必须调整数组的大小。为了避免在知道要添加大量元素时调整大小的高成本,可以使用更高的初始容量构造 ArrayList

如果使用数据结构透视图来理解这两个结构,那么 LinkedList 基本上是一个包含头节点的顺序数据结构。Node 是两个组件的包装器: 一个类型为 T 的值[通过泛型接受]和另一个链接到它的 Node 引用。因此,我们可以断言它是一个递归数据结构(Node 包含另一个 Node,它有另一个 Node 等等)。如上所述,在 LinkedList 中添加元素需要线性时间。

ArrayList 是一个可生长的数组。它就像一个正则数组。在引擎盖下面,当添加一个元素时,ArrayList 已经满负荷运行,它将创建另一个大小大于以前大小的数组。然后将元素从前一个数组复制到新的数组,并将要添加的元素也放置在指定的索引处。

ArrayList是你想要的。 LinkedList几乎总是一个(性能)错误。

为什么 LinkedList糟糕透顶:

  • 它使用大量小型内存对象,因此会影响整个进程的性能。
  • 许多小对象不利于缓存区域性。
  • 任何索引操作都需要遍历,即具有 O (n)性能。这在源代码中并不明显,导致算法 O (n)比使用 ArrayList时要慢。
  • 获得好的表现是很棘手的。
  • 即使 big-O 的性能与 ArrayList相同,它也可能会显著降低性能。
  • 在源代码中看到 LinkedList是不和谐的,因为它可能是错误的选择。

如果你的代码有 add(0)remove(0),使用 LinkedList,它是更漂亮的 addFirst()removeFirst()方法。否则,使用 ArrayList

当然,番石榴不可变列表是你最好的朋友。

是的,我知道,这是个古老的问题,但是我要提出我的意见:

LinkedList 是 几乎总是的错误选择,就性能而言。有一些非常特殊的算法需要调用 LinkedList,但是这些算法非常非常罕见,而且一旦你使用 ListIterator 导航,这种算法通常会特别依赖于 LinkedList 在列表中间相对快速地插入和删除元素的能力。

LinkedList 的性能优于 ArrayList 的一个常见用例是: 队列的性能。然而,如果你的目标是性能,你也应该考虑使用 ArrayBlockingQueue (如果你能够提前确定队列大小的上限,并且能够提前分配所有的内存) ,或者这个 循环数组列表实现。(是的,它是从2001年开始的,所以您需要对它进行泛化,但是我得到的性能比率与刚才文章中在最近的 JVM 中引用的性能比率相当)

链表的一个重要特性(我在另一个答案中没有读到)是两个链表的连接。对于一个数组,这是 O (n)(+ 一些重新分配的开销) ,链表只是 O (1)或 O (2) ; -)

重点 : 对于 Java 来说,它的 LinkedList是不正确的!请参阅 < a href = “ https://stackoverflow. com/q/2494031/587642”> 在 Java 中有一种用于链表的快速 concat 方法吗?

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)

算法: Big-Oh 符号 (归档)

ArrayList 适用于 write-once-read-many 或者 appender,但不适合从前面或中间添加/删除。

数组列表本质上是一个包含添加项等方法的数组(而且应该使用泛型列表)。它是可以通过索引器(例如[0])访问的项的集合。它意味着从一个项目到下一个项目的进展。

链表指定从一个项目到下一个项目(项目 a-> 项目 b)的进度。对于数组列表也可以得到同样的效果,但是链表绝对会说明前一个列表后面应该跟着什么条目。

参见原答案下方作者2021年更新。


原始答案(2011)

作为一个在大规模 SOA Web 服务上从事操作性能工程已经有十年的人,我更喜欢 LinkedList 的行为而不是 ArrayList。虽然 LinkedList 的稳态吞吐量更差,因此可能导致购买更多的硬件——数组列表在压力下的行为可能导致集群中的应用程序以近乎同步的方式扩展它们的数组,对于较大的数组大小可能导致应用程序缺乏响应和中断,而在压力下,这是灾难性的行为。

类似地,你可以从默认吞吐量终身垃圾收集器中获得更好的应用吞吐量,但是一旦你得到了10GB 的 Java 应用,你可以在一个完整的 GC 期间锁定应用25秒,这会导致 SOA 应用的超时和失败,如果它发生得太频繁,你的 SLA 就会崩溃。尽管 CMS 收集器需要更多的资源,并且不能实现相同的原始吞吐量,但是它是一个更好的选择,因为它具有更多的可预测性和更小的延迟。

如果您所指的性能仅仅是吞吐量,并且可以忽略延迟,那么 ArrayList 只是一个更好的性能选择。根据我的工作经验,我不能忽视最坏情况下的潜伏期。

更新(2021年8月27日——10年后)

这个答案(我历史上最赞成的关于 SO 的答案)很可能是错误的(原因在下面的评论中概述)。我想补充的是,ArrayList 将优化顺序读取内存,并尽量减少缓存线路和 TLB 丢失等。通过比较,数组超过边界时的复制开销可能是无关紧要的(并且可以通过有效的 CPU 操作来完成)。考虑到硬件的发展趋势,这个问题的答案可能会越来越糟糕。一个 LinkedList 可能有意义的唯一情况是,你有成千上万个 List,其中任何一个都可能增长到 GB 大小,但是在分配列表的时候无法做出正确的猜测,并将它们全部设置为 GB 大小,这种情况下,这些列表就会爆炸。如果你发现了类似的问题,那么无论你的解决方案是什么,它确实需要重新设计(我不喜欢轻率地建议重新设计旧代码,因为我自己维护成堆的旧代码,但那将是一个非常好的例子,最初的设计已经走出了跑道,确实需要扔掉)。不过我还是会把我几十年来的糟糕观点留给你看。简单,合乎逻辑,而且大错特错。

正确或错误: 请在本地执行测试并自行决定!

编辑/删除在 LinkedList中比在 ArrayList中快。

ArrayListArray支持,需要两倍的大小,在大容量应用中表现更差。

下面是每个操作的单元测试结果。计时以纳秒为单位。


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 而不是 ArrayList 来分享我的观点:

每当我有一个方法返回从数据库获得的数据列表时,我总是使用 LinkedList。

我的理由是,因为不可能准确地知道我得到了多少结果,所以不会浪费内存(就像在 ArrayList 中,容量和实际元素数量之间存在差异) ,也不会浪费时间去复制容量。

至于 ArrayList,我同意至少应该始终使用具有初始容量的构造函数,以尽可能减少数组的重复。

到目前为止,除了 LinkedListArrayList“多得多”这一普遍共识之外,似乎没有人解决这些列表中每个列表的内存占用问题,所以我做了一些数字处理来证明这两个列表究竟占用了 N 个空引用的多少。

由于引用在它们的相对系统上是32位或64位(即使为空) ,所以我为32位和64位的 LinkedListsArrayLists包含了4组数据。

注意: ArrayList行显示的大小是为 修剪过的清单-在实践中,ArrayList中后台阵列的容量通常大于其当前的元素计数。

注2: (感谢 BeeOnRope)由于 CompresseOops 现在是默认的,从 JDK6中期开始,以下64位机器的值基本上与它们的32位机器匹配,除非你特意关闭它。


Graph of LinkedList and ArrayList No. of Elements x Bytes


结果清楚地表明,LinkedListArrayList多很多,特别是元素含量非常高。如果内存是一个因素,避开 LinkedLists

我使用的公式如下,让我知道,如果我做错了什么,我会修复它。对于32位或64位系统,“ b”是4或8,“ n”是元素数。请注意 mods 的原因是因为 Java 中的所有对象都将占用8个字节的空间,而不管是否全部使用。

数组列表:

ArrayList object header + size integer + modCount integer + array reference + (array oject header + b * n) + MOD(array oject, 8) + MOD(ArrayList object, 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 object header + size integer + modCount integer + reference to header + reference to footer + (node object overhead + reference to previous element + reference to next element + reference to element) * n) + MOD(node object, 8) * n + MOD(LinkedList object, 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)

我应该什么时候使用 LinkedList? 当大多数时候使用堆栈,或者使用缓冲区。 我应该什么时候使用 ArrayList?只有在使用索引时,否则可以使用带有链表的 HashTable,然后得到:

哈希表 + 链表

  • 按键 O (1) ,进入
  • 按键 O (1) ,插入
  • 按键 O (1)移除
  • 并且在使用版本控制时,有一个用 O (1)实现 RemoveAll/SetAll 的技巧

这似乎是一个不错的解决方案,而且在大多数情况下,你都应该知道: HashTable 占用了大量的磁盘空间,因此当您需要管理1,000,000个元素列表时,它可以成为一个重要的东西。这种情况可能发生在服务器实现中,在客户机中很少发生。

再看看 红黑树

  • 随机访问 日志(n)、,
  • 插入 日志(n)、,
  • 移除 日志(n)

下面是 ArrayListLinkedList以及 CopyOnWrite-ArrayList中的大 O 符号:

数组列表

get                 O(1)
add                 O(1)
contains            O(n)
next                O(1)
remove              O(n)
iterator.remove     O(n)

LinkedList

get                 O(n)
add                 O(1)
contains            O(n)
next                O(1)
remove              O(1)
iterator.remove     O(1)

CopyOnWrite-数组列表

get                 O(1)
add                 O(n)
contains            O(n)
next                O(1)
remove              O(n)
iterator.remove     O(n)

基于这些,你必须决定选择什么。 :)

我知道这是一篇老文章,但我真的不敢相信没有人提到 LinkedList实现了 Deque。只需看看 Deque(和 Queue)中的方法; 如果您想进行公平的比较,请尝试对 ArrayDeque运行 LinkedList并进行特性对特性的比较。

ArrayList本质上是一个数组。 LinkedList实现为一个双链表。

get很清楚。O (1)表示 ArrayList,因为 ArrayList允许使用索引进行随机访问。O (n)表示 LinkedList,因为它需要先找到索引。注意: addremove有不同的版本。

LinkedList在添加和删除方面更快,但在获取方面更慢。简而言之,如果:

  1. 元素没有大量的随机存取
  2. 有大量的添加/删除操作

= = 数组列表 = =

  • 加(E)
    • 在数组列表的末尾添加
    • 需要调整内存大小的成本。
    • O (n)最坏的情况,O (1)分期偿还
  • Add (int index,E 元素)
    • 添加到特定的索引位置
    • 需要移位和可能的内存调整成本
    • O (n)
  • 移除(int 索引)
    • 删除指定的元素
    • 需要移位和可能的内存调整成本
    • O (n)
  • 移除(对象)
    • 从列表中删除指定元素的第一个匹配项
    • 需要首先搜索元素,然后移位和可能的内存调整成本
    • O (n)

= = LinkedList = =

  • 加(E)

    • 添加到列表的末尾
    • O (1)
  • Add (int index,E 元素)

    • 在指定位置插入
    • 需要先找到位置
    • O (n)
  • 除去()
    • 删除列表的第一个元素
    • O (1)
  • 移除(int 索引)
    • 移除具有指定索引的元素
    • 需要先找到元素
    • O (n)
  • 移除(对象)
    • 删除指定元素的第一个匹配项
    • 需要先找到元素
    • O (n)

下面是来自 Programcreek.com的图(addremove是第一种类型,也就是说,在列表的末尾添加一个元素,并删除列表中指定位置的元素。):

enter image description here

数组列表中的 get (i)操作比 LinkedList 快,因为:
ArrayList: Resizable-List 接口的数组实现
LinkedList: List 和 Deque 接口的双链表实现

将索引编入列表的操作将从开始或结束遍历列表,以较接近指定索引的位置为准。

对于 ArrayList 和 LinkedList,remove()insert()的运行时效率都是 O (n)。然而,线性处理时间背后的原因来自两个非常不同的原因:

在 ArrayList 中,您可以访问 O (1)中的元素,但是实际上移除或插入某些内容会使其成为 O (n) ,因为需要更改以下所有元素。

在 LinkedList 中,实际上需要 O (n)才能到达所需的元素,因为我们必须从最开始开始,直到到达所需的索引。实际上,移除或插入是常量,因为我们只需要为 remove()改变1个引用,为 insert()改变2个引用。

插入和删除两者中哪一个更快取决于它发生的位置。如果我们更接近开始,LinkedList 会更快,因为我们必须通过相对较少的元素。如果我们更接近结束,数组列表会更快,因为我们到达那里的时间是恒定的,只需要改变它后面的少数剩余元素。如果精确地在中间完成,LinkedList 会更快,因为遍历 n 个元素比移动 n 个值更快。

好处: 虽然没有办法为数组列表创建这两个方法 O (1) ,但实际上在 LinkedList 中有一种方法可以做到这一点。假设我们希望在执行的过程中完成整个 List 的删除和插入操作。通常,您可以从使用 LinkedList 的每个元素的最初开始,我们还可以“保存”使用迭代器处理的当前元素。在迭代器的帮助下,当我们在 LinkedList 中工作时,我们得到了 remove()insert()的 O (1)效率。让它成为我所知道的唯一性能优势,LinkedList 总是比 ArrayList 好。

ArrayListLinkedList都实现了 List interface,它们的方法和结果几乎相同。然而,它们之间几乎没有什么区别,这取决于需求。

数组列表 VS LinkedList

1) Search:ArrayList搜索操作比 LinkedList的搜索操作快得多。ArrayList中的 get(int index)给出了 O(1)的性能,而 LinkedList的性能是 O(n)

Reason: ArrayList为其元素维护基于索引的系统,因为它隐式地使用数组,这使得它能够更快地搜索列表中的元素。另一方面,LinkedList实现了双向链表,需要遍历所有元素以搜索一个元素。

2) Deletion: LinkedList移除操作使 O(1)具有性能,而 ArrayList具有可变性能: 最坏情况下是 O(n)(移除第一个元素) ,最好情况下是 O(1)(移除最后一个元素)。

结论: 删除 LinkedList 元素比 数组列表。

原因: LinkedList 的每个元素维护两个指向列表中两个相邻元素的指针(地址)。因此,删除只需要更改要删除的节点的两个相邻节点(元素)中的指针位置。而在 ArrayList 中,所有的元素都需要移动,以填充被移除的元素创建的空间。

3) Inserts Performance:LinkedList加法给 O(1)的性能,而 ArrayListO(n)的最坏情况。原因与删除的解释相同。

4) Memory Overhead: ArrayList维护索引和元素数据,LinkedList维护元素数据和邻居节点的两个指针

因此 LinkedList 中的内存消耗相对较高。

这些类别之间没有什么相似之处,具体如下:

  • ArrayList 和 LinkedList 都是 List 接口的实现。
  • 它们都维护元素插入顺序,这意味着在显示 ArrayList 和 LinkedList 元素时,结果集的顺序与元素插入 List 的顺序相同。
  • 这两个类都是非同步的,可以使用 Collections.synizedList 方法显式地进行同步。
  • 这些类返回的 iteratorlistIteratorfail-fast(如果在创建迭代器之后的任何时候列表在结构上被修改,除了通过 iterator’s自己的移除或添加方法之外,迭代器将 throw a ConcurrentModificationException)。

什么时候使用 LinkedList,什么时候使用 ArrayList?

  • 如上所述,与 ArrayList(O(n))相比,插入和删除操作在 LinkedList中提供了良好的性能 (O(1))

    因此,如果在应用程序中需要频繁添加和删除,那么 LinkedList 是最佳选择。

  • 搜索(get method)操作在 Arraylist (O(1))中是快速的,但在 LinkedList (O(n))中不是

    因此,如果有较少的添加和删除操作和更多的搜索操作要求,数组列表将是您的最佳选择。

让我们比较一下 LinkedList 和 ArrayList:

1. 实施

ArrayList 是 list 接口的可调数组实现,而

LinkedList 是 list 接口的双链表实现。


2. 表现

  • 获取(int index)或搜索操作

    数组列表 get (int index)操作在常量时间内运行,即 O (1)

    LinkedList get (int index)操作运行时为 O (n)。

    数组列表比 LinkedList 快的原因是 ArrayList 使用一个基于索引的系统来表示它的元素,因为它在内部使用一个数组,

    LinkedList 不为其元素提供基于索引的访问,因为它从开始或结束(以较近者为准)迭代以检索指定元素索引处的节点。

  • Insert ()或 add (Object)操作

    LinkedList中的插入通常比 ArrayList 快。在 LinkedList 中,添加或插入是 O (1)操作。

    而在 数组列表中,如果数组是完整的,即最坏的情况,那么调整数组大小和将元素复制到新数组中会有额外的开销,这使得在 ArrayList O (n)中添加操作的运行时间为 O (1)。

  • 删除(int)操作

    LinkedList 中的删除操作通常与 ArrayList 相同,即 O (n)。

    LinkedList中,有两个重载的移除方法。一个是 delete () ,没有任何参数,它移除列表的头部,并在常量时间内运行 O (1)。LinkedList 中的另一个重载删除方法是 delete (int)或 remove (Object) ,它删除作为参数传递的 Object 或 int。此方法遍历 LinkedList,直到找到 Object 并将其从原始列表中取消链接。因此此方法运行时为 O (n)。

    而在 数组列表中,remove (int)方法涉及将元素从旧数组复制到新更新的数组,因此其运行时为 O (n)。


3. 反向迭代器

LinkedList 可以在相反的方向上进行迭代

数组列表中没有下行迭代器() ,所以我们需要编写自己的代码来反向迭代 ArrayList。


4. 初始容量

如果构造函数没有重载,那么 数组列表创建一个初始容量为10的空列表,而

LinkedList 只构造没有任何初始容量的空列表。


5. 内存开销

与 ArrayList 相比,LinkedList中的内存开销更大,因为 LinkedList 中的节点需要维护下一个节点和上一个节点的地址。同时

数组列表中,每个索引只包含实际的对象(数据)。


来源

约书亚•布洛赫(Joshua Bloch)是 LinkedList 的作者:

真的有人用 LinkedList 吗? 我写的,我从来不用。

链接: https://twitter.com/joshbloch/status/583813919019573248

我很抱歉这个答案没有其他答案那么有用,但是我认为如果不能说明问题的话,这个答案就是最好的解释了。

ArrayList 和 LinkedList 各有利弊。

ArrayList 使用连续内存地址,而 LinkedList 使用指向下一个节点的指针。所以当你想在数组列表中查找一个元素的时候比用 LinkedList 进行 n 次迭代要快。

另一方面,在 LinkedList 中插入和删除要容易得多,因为您只需更改指针,而 ArrayList 意味着对任何插入或删除都使用 shift 操作。

如果你经常在你的应用程序中使用数组列表进行检索操作。如果经常插入和删除,请使用 LinkedList。

ArrayList 扩展了 AbstractList 并实现了 List 接口。ArrayList 是动态数组。可以说,它基本上是为了克服数组 < br > 的缺点而创建的
LinkedList 类扩展 AbstractSequentialList 并实现 List、 Deque 和 Queue 接口。
表演
arraylist.get()是 O (1) ,而 linkedlist.get()是 O (n)
arraylist.add()是 O (1) ,linkedlist.add()是0(1)
arraylist.contains()是 O (n) ,linkedlist.contains()是 O (n)
arraylist.next()是 O (1) ,linkedlist.next()是 O (1)
arraylist.remove()是 O (n) ,而 linkedlist.remove()是 O (1)
在数组表 < br > iterator.remove()是 O (n) < br > 而在 Linkedlist < br > iterator.remove()是 O (1)

1)基本资料结构

ArrayList 和 LinkedList 的第一个区别是 ArrayList 由 Array 支持,而 LinkedList 由 LinkedList 支持。这将导致进一步的性能差异。

2) LinkedList 实现 Deque

ArrayList 和 LinkedList 的另一个不同之处在于,除了 List 接口之外,LinkedList 还实现了 Deque 接口,它为 add()poll()以及其他一些 Deque 函数提供了先入先出操作。3)在数组列表中添加元素在数组列表中添加元素是 O (1)操作,如果它没有触发数组的大小调整,在这种情况下它变成 O (log (n))。另一方面,在 LinkedList 中添加元素是 O (1)操作,因为它不需要任何导航。

4)从一个位置移除一个元素

为了从一个特定的索引中删除一个元素,例如,通过调用 remove(index),ArrayList 执行一个拷贝操作,使其接近 O (n) ,而 LinkedList 需要遍历该点,这也使其成为 O (n/2) ,因为它可以根据邻近程度从任一方向遍历。

5)在 ArrayList 或 LinkedList 上迭代

迭代是 LinkedList 和 ArrayList 的 O (n)操作,其中 n 是一个元素的数目。

6)从一个位置检索元素

get(index)操作在 ArrayList 中为 O (1) ,而在 LinkedList 中为 O (n/2) ,因为它需要遍历到该条目。但是,在大 O 符号中 O (n/2)只是 O (n) ,因为我们忽略了那里的常数。

7)记忆

LinkedList 使用一个包装器对象 Entry,它是一个静态嵌套类,用于存储数据和前后两个节点,而 ArrayList 只在 Array 中存储数据。

所以对于 ArrayList 来说,内存需求似乎比 LinkedList 少,除了 Array 在将内容从一个 Array 复制到另一个 Array 时执行重新调整大小的操作。

如果 Array 足够大,它可能会占用大量内存,并触发垃圾收集,这可能会减慢响应时间。

从以上数组列表和 LinkedList 的区别来看,数组列表在几乎所有情况下都是比 LinkedList 更好的选择,除非你比 remove()或者 get()更频繁地执行 add()操作。

修改链表比修改 ArrayList 更容易,特别是在从开始或结束添加或删除元素时,因为链表在内部保存这些位置的引用,并且可以在 O (1)时间内访问它们。

换句话说,您不需要遍历链表来到达您想要添加元素的位置,在这种情况下,加法就变成了 O (n)操作。例如,在链表中间插入或删除元素。

在我看来,在 Java 中使用 ArrayList 而不是 LinkedList 可以达到大多数实际目的。

我在这里看到的一个测试只进行一次。但是我注意到,您需要多次运行这些测试,最终它们的次数将会收敛。基本上 JVM 需要预热。对于我的特殊用例,我需要将项目添加/删除到一个增加到大约500个项目的列表中。在我的测试中,LinkedList出来得更快,与 LinkedList来在大约50,000 NS 和 ArrayList来在大约90,000 NS... 左右。请参阅下面的代码。

public static void main(String[] args) {
List<Long> times = new ArrayList<>();
for (int i = 0; i < 100; i++) {
times.add(doIt());
}
System.out.println("avg = " + (times.stream().mapToLong(x -> x).average()));
}


static long doIt() {
long start = System.nanoTime();
List<Object> list = new LinkedList<>();
//uncomment line below to test with ArrayList
//list = new ArrayList<>();
for (int i = 0; i < 500; i++) {
list.add(i);
}


Iterator it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
long end = System.nanoTime();
long diff = end - start;
//uncomment to see the JVM warmup and get faster for the first few iterations
//System.out.println(diff)
return diff;
}

由于现代计算机体系结构,对于几乎所有可能的用例,ArrayList都将显著提高效率,因此除了一些非常独特和极端的情况外,应该避免使用 LinkedList


理论上,LinkedList 的 add(E element)是 O (1)

在列表的中间添加一个元素应该是非常有效的。

实践是非常不同的,因为 LinkedList 是一个 缓存敌人数据结构。从性能角度来看,很少有情况下 LinkedList的性能会优于 缓存友好型ArrayList

下面是在随机位置插入元素的基准测试结果。正如你所看到的-数组列表,如果更有效,虽然在理论上,每次插入列表中间将需要“移动”数组的 N后面的元素(较低的值是更好的) :

enter image description here

使用下一代硬件(更大、更高效的缓存)的结果更加确凿:

enter image description here

完成同样的工作需要花费更多的时间

造成这种情况的主要原因有两个:

  1. 主要是 -LinkedList的节点在内存中随机分布。RAM (“随机访问存储器”)并不是真正的随机存储器,需要获取内存块来缓存。这种操作需要时间,而且当这种获取频繁发生时——缓存中的内存页需要全部替换—— > 缓存丢失—— > 缓存效率不高。 ArrayList元素存储在连续内存中——这正是现代 CPU 体系结构正在优化的地方

  2. 次要 LinkedList需要保留后退/前进指针,这意味着每存储一个值所消耗的内存是 ArrayList的3倍。

顺便说一下,DynamicIntArray 是一个定制的 ArrayList 实现,其中包含 Int(原始类型)而不是 Objects ——因此所有数据实际上都是相邻存储的——因此效率更高。

需要记住的一个关键因素是,获取内存块的成本,比访问单个内存单元的成本更重要。这就是为什么读取器1MB 的顺序内存要比从不同内存块读取这么多数据快400倍的原因:

Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference                           0.5 ns
Branch mispredict                            5   ns
L2 cache reference                           7   ns                      14x L1 cache
Mutex lock/unlock                           25   ns
Main memory reference                      100   ns                      20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy             3,000   ns        3 us
Send 1K bytes over 1 Gbps network       10,000   ns       10 us
Read 4K randomly from SSD*             150,000   ns      150 us          ~1GB/sec SSD
Read 1 MB sequentially from memory     250,000   ns      250 us
Round trip within same datacenter      500,000   ns      500 us
Read 1 MB sequentially from SSD*     1,000,000   ns    1,000 us    1 ms  ~1GB/sec SSD, 4X memory
Disk seek                           10,000,000   ns   10,000 us   10 ms  20x datacenter roundtrip
Read 1 MB sequentially from disk    20,000,000   ns   20,000 us   20 ms  80x memory, 20X SSD
Send packet CA->Netherlands->CA    150,000,000   ns  150,000 us  150 ms

资料来源: 每个程序员都应该知道的延迟数

为了使这一点更加清楚,请检查向列表开头添加元素的基准。这是一个用例,在理论上,LinkedList应该真正发光,而 ArrayList应该呈现糟糕甚至更糟糕的结果:

enter image description here

注意: 这是 C + + 标准库的基准,但是我以前的经验表明 C + + 和 Java 的结果非常相似。源代码

复制顺序大容量内存是一种由现代 CPU 改变理论优化的操作,实际上使得 ArrayList/Vector更加高效


编辑: 所有的基准张贴在这里是由 Kjell Hedström创建。甚至更多的数据可以在 他的博客上找到

您可以根据要在该特定 List 上执行的操作的时间复杂性使用其中一个。

|---------------------|---------------------|--------------------|------------|
|      Operation      |     ArrayList       |     LinkedList     |   Winner   |
|---------------------|---------------------|--------------------|------------|
|     get(index)      |       O(1)          |         O(n)       | ArrayList  |
|                     |                     |  n/4 steps in avg  |            |
|---------------------|---------------------|--------------------|------------|
|      add(E)         |       O(1)          |         O(1)       | LinkedList |
|                     |---------------------|--------------------|            |
|                     | O(n) in worst case  |                    |            |
|---------------------|---------------------|--------------------|------------|
|    add(index, E)    |       O(n)          |         O(n)       | LinkedList |
|                     |     n/2 steps       |      n/4 steps     |            |
|                     |---------------------|--------------------|            |
|                     |                     |  O(1) if index = 0 |            |
|---------------------|---------------------|--------------------|------------|
|  remove(index, E)   |       O(n)          |         O(n)       | LinkedList |
|                     |---------------------|--------------------|            |
|                     |     n/2 steps       |      n/4 steps     |            |
|---------------------|---------------------|--------------------|------------|
|  Iterator.remove()  |       O(n)          |         O(1)       | LinkedList |
|  ListIterator.add() |                     |                    |            |
|---------------------|---------------------|--------------------|------------|




|--------------------------------------|-----------------------------------|
|              ArrayList               |            LinkedList             |
|--------------------------------------|-----------------------------------|
|     Allows fast read access          |   Retrieving element takes O(n)   |
|--------------------------------------|-----------------------------------|
|   Adding an element require shifting | o(1) [but traversing takes time]  |
|       all the later elements         |                                   |
|--------------------------------------|-----------------------------------|
|   To add more elements than capacity |
|    new array need to be allocated    |
|--------------------------------------|

我的经验法则是,如果我需要一个 Collection(即不需要是一个 List) ,然后使用 ArrayList,如果你事先知道的大小,或者可以自信地知道大小,或者知道它不会有太大的变化。如果您需要随机访问(即使用 get(index)) ,那么请避免使用 LinkedList。基本上,只有在不需要索引访问且不知道所分配集合的(大致)大小时,才使用 LinkedList。另外,如果你打算添加和删除很多东西(同样是通过 Collection界面) ,那么 LinkedList 可能是更好的选择。

首先使用 Vector 代替 ArrayList,因为可以重写 insureCapasity 方法, 在 ArrayList 中是私有的,并且添加当前数组的1.5大小 < a href = “ https://docs.oracle.com/javase/8/docs/api/java/util/Vector.html # ensure卷容量-int-”rel = “ nofollow norefrer”> https://docs.oracle.com/javase/8/docs/api/java/util/vector.html#ensurecapacity-int-

在许多情况下,它可以更好的 linkedList,最大的优势之一,你插入数据的高频率,所以你的列表大小变化非常快,你不能为数字元素分配大小。 在理论上,你可能会出现“内存不足”这样的错误,但在现代计算机中,你有16G 和交换磁盘,所以如果你列出的是十亿元素,你可能会失败,比较15-20年前。