何时在 Java 中使用 LinkedList 而不是 ArrayList?

Posted

技术标签:

【中文标题】何时在 Java 中使用 LinkedList 而不是 ArrayList?【英文标题】:When to use LinkedList over ArrayList in Java? 【发布时间】:2010-09-24 06:37:34 【问题描述】:

我一直是一个简单易用的人:

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

我使用接口作为可移植性的类型名称,这样当我提出诸如此类的问题时,我就可以重新编写我的代码。

什么时候应该使用LinkedList 而不是ArrayList,反之亦然?

【问题讨论】:

另见:Array versus linked-list 只要看看 LinkedList 的作者***.com/a/42529652/2032701 的引用,你就会对这个问题有一个实际的认识。 【参考方案1】:

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

Array vs ArrayList vs LinkedList vs Vector 更深入,同样如此 Linked List.

【讨论】:

这里值得一提的是,LinkedList 仅在第一个和最后一个位置添加/删除速度很快 - 那么复杂度将为 O(1),但在中间添加仍然是 O(n ),因为我们需要遍历 LinkedList 的大约 n/2 个元素。 是吗? Why is an ArrayList always faster than a LinkedList 发现向 ArrayList 添加 10M 项比向 LinkedList 添加 10M 项要快。 (即 ArrayList 在最后添加时更快,有时需要重新分配。)【参考方案2】:

ArrayList 可以随机访问,而LinkedList 扩展和删除元素的成本非常低。大多数情况下,ArrayList 没问题。

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

【讨论】:

LinkedList 添加元素并不便宜。将一百万个元素添加到 ArrayList 几乎总是比将它们添加到 LinkedList 更快。而现实世界代码中的大多数列表甚至都不到一百万个元素。 在任何时候,您都知道将项目添加到您的 LinkedList 的成本。你没有的 ArrayList(一般来说)。将单个项目添加到包含一百万个项目的 ArrayList 可能需要很长时间 - 这是一个 O(n) 操作加上双倍的存储空间,除非您预先分配空间。将项目添加到 LinkedList 是 O(1)。我最后的声明成立。 将单个项目添加到 ArrayList 中,无论是 100 万还是 10 亿,都是 O(1)。将项目添加到 LinkedList 也是 O(1)。 “添加”意味着添加到末尾。 您对实现的理解一定与我不同。根据我的经验,复制 10 亿个元素的数组比复制一个 100 万个元素的数组需要更长的时间。 @kachanov 你一定误解了达斯汀。除非您声明了一个包含 10 亿个项目的数组,否则您最终需要调整数组的大小,在这种情况下,您需要将所有元素复制到一个新的更大的数组中,因此有时您会得到 O (N),但是使用链表,您将始终得到 O (1)【参考方案3】:

这取决于您将在列表上执行更多操作。

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

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

【讨论】:

更多内容不用看,直接写代码。你会发现ArrayList的实现在插入和删除方面比LinkedList快。【参考方案4】:

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

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

【讨论】:

【参考方案5】:

总结ArrayListArrayDeque许多 的用例中比LinkedList 更可取。如果您不确定,请从 ArrayList 开始。


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

LinkedListArrayList 是 List 接口的两种不同实现。 LinkedList 使用双向链表实现它。 ArrayList 使用动态调整大小的数组来实现它。

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

对于LinkedList&lt;E&gt;

get(int index)O(n)(平均有 n/4 步),但是当 index = 0 或 @ 时 O(1) 987654334@(在这种情况下,您还可以使用getFirst()getLast())。 主要好处之一 LinkedList&lt;E&gt; add(int index, E element)O(n)(平均有 n/4 步),但是当 index = 0 或 @ 时 O(1) 987654340@(在这种情况下,您还可以使用addFirst()addLast()/add())。 主要好处之一 LinkedList&lt;E&gt; remove(int index)O(n)(平均有 n/4 步),但是当 index = 0 或 @ 时 O(1) 987654347@(在这种情况下,您也可以使用removeFirst()removeLast())。 主要好处之一 LinkedList&lt;E&gt; Iterator.remove()O(1)主要好处之一 LinkedList&lt;E&gt; ListIterator.add(E element)O(1)主要好处之一 LinkedList&lt;E&gt;

注意:许多操作平均需要 n/4 步,最佳情况下需要 恒定 步数(例如 index = 0),并且n/2 步在最坏的情况下(列表中间)

对于ArrayList&lt;E&gt;

get(int index)O(1)主要好处 ArrayList&lt;E&gt; 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&lt;E&gt; 允许使用迭代器进行恒定时间的插入或删除,但只能顺序访问元素。换句话说,您可以向前或向后遍历列表,但在列表中查找位置所花费的时间与列表的大小成正比。 Javadoc 说 “索引到列表中的操作将从开头或结尾遍历列表,以较近者为准”,因此这些方法是 O(n) (n/4 步),但对于index = 0O(1)

另一方面,ArrayList&lt;E&gt; 允许快速随机读取访问,因此您可以在恒定时间内抓取任何元素。但是从任何地方添加或删除,但最后需要将所有后面的元素转移过来,要么打开一个开口,要么填补空白。另外,如果你添加的元素多于底层数组的容量,则会分配一个新数组(大小的 1.5 倍),并将旧数组复制到新数组,因此添加到 ArrayListO (n) 在最坏的情况下,但平均保持不变。

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

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

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

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

ArrayList 的默认初始容量非常小(Java 1.4 - 1.8 为 10)。但是由于底层实现是一个数组,如果添加很多元素,则必须调整数组的大小。当您知道要添加大量元素时,为避免调整大小的高成本,请构造具有更高初始容量的ArrayList

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

ArrayList 是一个可增长的数组。它就像一个常规数组。在幕后,当添加一个元素并且 ArrayList 已经满载时,它会创建另一个大小大于先前大小的数组。然后将元素从先前的数组复制到新的数组中,并且要添加的元素也被放置在指定的索引处。

【讨论】:

很多人忘记的一件事是 ArrayList 在内存中是紧凑的,这意味着它比 LinkedList 对缓存更友好。 LinkedList 可以分布在整个 RAM 中,而 ArrayList 总是紧贴在一起以利用空间局部性。这对现实世界有重要的影响。【参考方案6】:

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

为什么LinkedList 很烂:

它使用大量小内存对象,因此会影响整个进程的性能。 大量小对象不利于缓存局部性。 任何索引操作都需要遍历,即具有 O(n) 性能。这在源代码中并不明显,导致算法比使用 ArrayList 时慢 O(n)。 获得良好的性能并不容易。 即使 big-O 性能与 ArrayList 相同,它也可能会明显变慢。 在源代码中看到 LinkedList 令人不快,因为这可能是错误的选择。

【讨论】:

【参考方案7】:

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

当然,Guava 的 ImmutableList 是你最好的朋友。

【讨论】:

对于小列表,ArrayList.add(0) 仍然总是比 LinkedList.addFirst() 快。 @Porculus 我经常听到这样的论点,即对于小列表 ArrayList.add(0) 会更快,这个小有多小? 10 个元素,1000 万,? @garg10may small 小于 10。 @Porculus small 表示小于 ArrayList 底层内部数组的最大容量。【参考方案8】:

是的,我知道,这是一个古老的问题,但我会投入两分钱:

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

LinkedList 优于 ArrayList 的一个常见用例是:队列。但是,如果您的目标是性能,而不是 LinkedList,您还应该考虑使用 ArrayBlockingQueue (如果您可以提前确定队列大小的上限,并且可以预先分配所有内存),或者这个@987654321 @。 (是的,它是从 2001 年开始的,因此您需要对其进行泛化,但我在最近的 JVM 中获得了与刚才文章中引用的性能比相当的性能比)

【讨论】:

从 Java 6 开始,您可以使用 ArrayDeque。 docs.oracle.com/javase/6/docs/api/java/util/ArrayDeque.html ArrayDequeLinkedList 慢,除非所有操作都在同一端。用作堆栈时还可以,但不能很好地排队。 不正确——至少对于 Oracle 在 jdk1.7.0_60 和以下测试中的实现。我创建了一个测试,其中循环了 1000 万次,并且我有一个包含 1000 万个随机整数的 Deque。在循环内部,我从中轮询一个元素并提供一个常量元素。在我的计算机上,LinkedList 比 ArrayDeque 慢 10 倍以上,并且使用的内存更少)。原因是与 ArrayList 不同,ArrayDeque 保留了一个指向数组头部的指针,因此当头部被移除时它不必移动所有元素。 ArrayDeque 用作堆栈时可能比 Stack 快,而用作队列时可能比 LinkedList 快。 请注意,akhil_mittal 的评论引用自 ArrayDeque 文档。【参考方案9】:

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

重要提示:对于 Java,LinkedList 这不是真的!见Is there a fast concat method for linked list in Java?

【讨论】:

怎么样?这可能适用于链表数据结构,但不适用于 Java LinkList 对象。您不能只将一个列表中的next 指向第二个列表中的第一个节点。唯一的方法是使用addAll() 按顺序添加元素,尽管它比循环并为每个元素调用add() 要好。要在 O(1) 中快速完成此操作,您需要一个合成类(如 org.apache.commons.collections.collection.CompositeCollection),但这适用于任何类型的列表/集合。 是的,没错。我相应地编辑了答案。但请参阅此答案以了解如何使用 LinkedList:***.com/questions/2494031/…【参考方案10】:
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)

Algorithms: Big-Oh Notation(已存档)

ArrayLists 适用于一次写入多次读取或追加器,但不适合从前面或中间添加/删除。

【讨论】:

如果不考虑常数因素,就无法直接比较大 O 值。对于小列表(而且大多数列表都很​​小),ArrayList 的 O(N) 比 LinkedList 的 O(1) 快。 我不关心小列表的性能,我的电脑也不关心除非它以某种方式在循环中使用。 LinkedList 不能真正插入到O(1) 的中间。它必须遍历列表的一半才能找到插入点。 LinkedList:插入中间 O(1) - 是错误的!我发现即使插入到 LinkedList 大小的 1/10 位置也比将元素插入到 ArrayList 的 1/10 位置要慢。更糟糕的是:收集结束。插入 ArrayList 的最后一个位置(不是最后一个)比 LinkedList 的最后一个位置(不是最后一个)更快 @kachanov 在LinkedList 中插入 O(1) 如果你有一个插入位置的迭代器,即ListIterator.add 应该是O(1)LinkedList【参考方案11】:

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

链表指定从一个项目到下一个项目的进展(项目 a -> 项目 b)。您可以使用数组列表获得相同的效果,但链表绝对说明了应该在前一个之后的项目。

【讨论】:

【参考方案12】:

作为一个在超大规模 SOA Web 服务上进行运营性能工程已有大约十年的人,我更喜欢 LinkedList 的行为而不是 ArrayList。虽然 LinkedList 的稳态吞吐量更差,因此可能会导致购买更多硬件 - ArrayList 在压力下的行为可能会导致集群中的应用程序以近乎同步的方式扩展其阵列,并且对于大阵列大小可能会导致缺乏响应能力在应用程序和中断,而在压力下,这是灾难性的行为。

同样,您可以通过默认吞吐量的终身垃圾收集器在应用程序中获得更好的吞吐量,但是一旦您获得具有 10GB 堆的 Java 应用程序,您可能会在 Full GC 期间锁定应用程序 25 秒,这会导致超时和失败在 SOA 应用程序中,如果它发生得太频繁,就会破坏您的 SLA。尽管 CMS 收集器占用更多资源并且无法实现相同的原始吞吐量,但它是一个更好的选择,因为它具有更高的可预测性和更小的延迟。

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

更新(2021 年 8 月 27 日——10 年后):这个答案(我在 SO 上历史上最受好评的答案)很可能是错误的(原因在下面的 cmets 中列出)。我想补充一点,ArrayList 将优化内存的顺序读取并最大限度地减少缓存行和 TLB 未命中等。相比之下,当数组增长超过界限时的复制开销可能是无关紧要的(并且可以通过高效的 CPU 操作来完成)。考虑到硬件趋势,随着时间的推移,这个答案也可能变得更糟。 LinkedList 可能有意义的唯一情况是高度做作的事情,您有数千个列表,其中任何一个都可能增长到 GB 大小,但在分配列表和设置它们时无法做出好的猜测所有到 GB 大小的都会炸毁堆。如果你发现了这样的问题,那么无论你的解决方案是什么,它确实需要重新设计(我不喜欢轻易建议重新设计旧代码,因为我自己维护成堆的旧代码,但那将是一个非常好的案例,原始设计只是跑出了跑道,确实需要被淘汰)。不过,我仍然会将我几十年前的糟糕观点留在那里供您阅读。简单、合乎逻辑且非常错误。

【讨论】:

难道不是另一种解决方案是通过使用 ArrayList 的 ensureCapacity() 方法以编程方式管理列表的大小?我的问题是为什么这么多东西被存储在一堆脆弱的数据结构中,而它们可能更好地存储在缓存或数据库机制中?前几天我接受了一次采访,他们上下发誓 ArrayList 的邪恶,但我来到这里,我发现复杂性分析是全方位的更好!不过,讨论的重点。谢谢! 一旦你获得了 10GB 堆的 java 应用程序,你可能会在导致超时的 Full GC 期间锁定应用程序 25 秒 实际上,使用 LinkedList 你会在full GCs,它必须在每个节点上迭代具有缓存未命中的过大 LinkedList。 那是……一个可怕的解决方案。你基本上依赖于 GC 为你清理,这非常昂贵,当你可以在 arraylist 上调用 ensureCapacity() 时...... @Holger 一个超出其容量递增的数组列表会分配一个多出 50% 空间的新列表。对于该增量,您需要 2.5 倍的内存(之后您可能需要完整的 gc 循环)。我不关心日常响应时间,我担心当高峰时间比昨天稍微重一点时堆内存用完,几个大数组列表决定他们需要一秒钟的 2.5 倍计数的空间或两个。在高峰使用期间,这种行为的一个实例使我整个月的 sla 都受到打击。 @Andreas: LinkedList always 分配的内存是普通引用数组的五倍,因此临时需要 2.5 倍的 ArrayList 仍然消耗更少的内存,即使没有回收内存。由于大数组分配绕过了 Eden 空间,它们不会对 GC 行为产生任何影响,除非内存确实不够,在这种情况下,LinkedList 会更早爆炸……【参考方案13】:

见the Java Tutorials - List Implementations。

【讨论】:

嗨@chharvey,仅链接的答案获得 6 个赞?请添加一些可以支持链接的点。如果 oracle 更改其链接怎么办?【参考方案14】:

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

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);
    

【讨论】:

ArrayList 不需要加倍,准确地说。请先检查来源。 应该指出,您的示例有缺陷...您正在从以下字符串中删除:18 + [2, 12] 个字节(“true0false”,“true500000false”),平均 25 个字节,它们是中间元素的大小。众所周知,随着元素字节大小的增加,链表性能更好,随着列表大小的增加,连续数组(列表)会做得更好。最重要的是,您正在对字符串执行 .equals() - 这不是一个便宜的操作。如果你改用整数,我认为会有区别。 "...在大容量应用中更糟糕":这是一个误解。 LinkedList 的内存开销要大得多,因为每个元素都有一个具有五个字段的节点对象。在许多产生 20 字节开销的系统上。 ArrayList 的每个元素的平均内存开销为 1 个半字,即 6 个字节,最坏情况下为 8 个字节。 我已经为你的基准测试做了一个更好的版本here, with results - 数组列表的追加性能对你来说是人为地低,因为 addAll 提供了一个完全初始大小的存储数组,所以第一个插入总是触发一个arraycopy。此外,这还包括预热周期,以便在收集数据之前进行 JIT 编译。 @BillK 从 Java 8 开始,您可以在适合的地方使用 removeIf(element -&gt; condition),与通过迭代器循环和删除相比,ArrayList 可以明显更快,因为它不需要移位每个单独元素的全部剩余部分。这是否比LinkedList 执行得更好或更差取决于特定场景,因为理论上LinkedList 是O(1),但是仅删除单个节点需要多次内存访问,这很容易超过@ 所需的数量987654334@ 删除大量元素时。【参考方案15】:

我已阅读回复,但在一种情况下,我总是在 ArrayList 上使用 LinkedList,我想分享以听取意见:

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

我的理由是,因为不可能确切知道我得到了多少结果,所以不会浪费内存(就像 ArrayList 中的容量和实际元素数量之间的差异一样),并且没有时间试图复制容量浪费了。

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

【讨论】:

【参考方案16】:

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

由于引用在其相关系统上是 32 位或 64 位(即使为空),因此我为 32 位和 64 位 LinkedListsArrayLists 添加了 4 组数据。

注意:ArrayList 行显示的大小适用于修剪列表 - 实际上,ArrayList 中的后备数组的容量通常更大比它当前的元素数。

注意 2: (感谢 BeeOnRope) 由于 CompressedOops 现在是 JDK6 中期及以上的默认值,因此以下 64 位机器的值将基本匹配它们的 32 位对应的,当然除非您专门将其关闭。



结果清楚地表明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 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)

【讨论】:

您的数学问题在于您的图表大大夸大了影响。您正在为每个对象建模,每个对象只包含一个int,因此是 4 或 8 个字节的数据。在链表中,基本上有 4 个“词”的开销。因此,您的图表给人的印象是链表使用了“五倍”的数组列表存储空间。这是错误的。开销是每个对象 16 或 32 字节,作为附加调整,而不是缩放因子。【参考方案17】:

什么时候应该使用LinkedList?主要使用堆栈或使用缓冲区时。 我应该什么时候使用ArrayList?只有在使用索引时,否则您可以将HashTable与链表一起使用,然后您会得到:

哈希表+链表

按键访问O(1), 按键插入O(1), 按键删除O(1) 在使用版本控制时,有一个技巧可以使用 O(1) 实现 RemoveAll / SetAll

这似乎是一个很好的解决方案,在大多数情况下,您应该知道: HashTable 占用大量磁盘空间,因此当您需要管理 1,000,000 个元素列表时,它可能变得很重要。这可能发生在服务器实现中,但在客户端很少发生。

也可以看看Red-Black-Tree

随机访问Log(n), 插入日志(n), 删除日志(n)

【讨论】:

我希望我能给这个多个 -1:LinkedList 在所有插入、删除和随机访问上都给出 O(N),因为您必须先遍历列表才能到达正确的点。此外,您可能会惊讶地发现 hashmap/table 像 ArrayList 一样使用调整数组大小,并且鉴于列表的最常见用法是仅由数字索引,使用 ArrayList 上的其中一个是最糟糕的主意。 @Numeron 你是对的,我不清楚我的回答。实际上,当您尝试按索引访问链表时,它将是 O(n),我的意思是按项目插入链表。然后是 O(1) 并且还与所有列表进行交互,它与数组列表相同。【参考方案18】:

这是ArrayListLinkedList 以及CopyOnWrite-ArrayList 中的Big-O 符号:

数组列表

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

链表

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

CopyOnWrite-ArrayList

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

根据这些,您必须决定选择什么。 :)

【讨论】:

>>>> ArrayList add --> O(1) LinkedList remove 不是 O(1),它需要搜索要删除的元素,因此最坏情况 O(n) 和平均 O(n/2) LinkedList.add() 也不是,尽管这里的大多数答案都是这样说的。【参考方案19】:

我知道这是一篇旧帖子,但我真的不敢相信没有人提到 LinkedList 实现了 Deque。看看Deque(和Queue)中的方法即可;如果您想要一个公平的比较,请尝试运行 LinkedListArrayDeque 并进行功能比较。

【讨论】:

【参考方案20】:

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

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

LinkedList 的添加和删除速度更快,但获取速度较慢。简而言之,LinkedList 应该是首选,如果:

    没有大量随机访问元素 有大量的添加/删除操作

=== 数组列表 ===

添加(E e) 在 ArrayList 末尾添加 需要调整内存大小的成本。 O(n) 最差,O(1) 摊销 添加(int 索引,E 元素) 添加到特定的索引位置 需要转移和可能的内存调整成本 O(n) 删除(整数索引) 移除指定元素 需要转移和可能的内存调整成本 O(n) 删除(对象o) 从此列表中删除指定元素的第一个匹配项 需要先搜索元素,然后移动和可能的内存调整成本 O(n)

=== 链接列表 ===

添加(E e)

添加到列表末尾 O(1)

add(int index, E element)

在指定位置插入 需要先找到位置 O(n) 删除() 删除列表的第一个元素 O(1) 删除(整数索引) 删除指定索引的元素 需要先找到元素 O(n) 删除(对象o) 删除第一次出现的指定元素 需要先找到元素 O(n)

这是来自programcreek.com的图(addremove是第一种,即在列表末尾添加一个元素,在列表中指定位置删除元素。):

【讨论】:

“LinkedList 比添加/删除更快”。错了,查看上面的答案***.com/a/7507740/638670【参考方案21】:

ArrayList 中的 get(i) 操作比 LinkedList 更快,因为:ArrayList: List 接口的 Resizable-array 实现LinkedList: 加倍-List 和 Deque 接口的链表实现

索引到列表中的操作将从开头或结尾遍历列表,以更接近指定索引的为准。

【讨论】:

【参考方案22】:

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

在 ArrayList 中,您可以在 O(1) 中找到元素,但实际上删除或插入某些内容会使它成为 O(n),因为需要更改以下所有元素。

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

两者中哪一个更快插入和删除取决于它发生的位置。如果我们更接近开始,LinkedList 会更快,因为我们必须通过相对较少的元素。如果我们更接近终点,ArrayList 会更快,因为我们在恒定时间内到达那里,并且只需要更改它后面的少数剩余元素。当恰好在中间完成时,LinkedList 会更快,因为遍历 n 个元素比移动 n 个值要快。

奖励:虽然没有办法使 ArrayList 的这两种方法 O(1),但实际上有一种方法可以在 LinkedLists 中做到这一点。假设我们要遍历整个 List 删除和插入元素。通常,您将从使用 LinkedList 的每个元素开始,我们也可以使用迭代器“保存”我们正在处理的当前元素。在 Iterator 的帮助下,我们在 LinkedList 中工作时获得了 remove()insert() 的 O(1) 效率。让它成为我知道 LinkedList 总是比 ArrayList 更好的唯一性能优势。

【讨论】:

【参考方案23】:

ArrayListLinkedList 都实现了List interface,它们的方法和结果几乎相同。但是,它们之间几乎没有区别,根据要求,它们会比另一种更好。

ArrayList 与 LinkedList

1) 与LinkedList 搜索操作相比,Search: ArrayList 搜索操作相当快。 get(int index) in ArrayList 给出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 add 方法给O(1) 性能,而ArrayList 在最坏的情况下给O(n)。原因与删除的解释相同。

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

因此LinkedList的内存消耗比较高。

这些类之间几乎没有相似之处,如下所示:

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

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

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

因此如果应用中需要频繁的增删改查,那么LinkedList是最好的选择。

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

所以如果添加和删除操作较少,搜索操作要求较多,ArrayList 将是您的最佳选择。

【讨论】:

【参考方案24】:

让我们比较一下 LinkedList 和 ArrayList w.r.t。以下参数:

1。实施

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

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


2。性能

get(int index) 或搜索操作

ArrayList get(int index) 操作在恒定时间内运行,即 O(1) 而

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

ArrayList 比 LinkedList 快的原因是 ArrayList 对其元素使用基于索引的系统,因为它在内部使用数组数据结构,另一方面,

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

insert() 或 add(Object) 操作

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

ArrayList中,如果数组是完整的,即最坏的情况,调整数组大小和将元素复制到新数组会产生额外的成本,这使得ArrayList中添加操作的运行时间为O( n),否则为 O(1)。

remove(int) 操作

LinkedList 中的删除操作一般和 ArrayList 相同,即 O(n)。

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

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


3。反向迭代器

LinkedList 可以使用descendingIterator() 反向迭代,而

ArrayList中没有descendingIterator(),所以我们需要编写自己的代码来反向迭代ArrayList。


4。初始容量

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

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


5。内存开销

LinkedList 中的内存开销比 ArrayList 更多,因为 LinkedList 中的节点需要维护下一个和前一个节点的地址。而

ArrayList中,每个索引只保存实际的对象(数据)。


Source

【讨论】:

【参考方案25】:

LinkedList 的作者 Joshua Bloch:

真的有人使用 LinkedList 吗?我写的,我从来没用过。

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

我很抱歉这个答案没有像其他答案那样提供丰富的信息,但我认为这将是最有趣和不言自明的。

【讨论】:

【参考方案26】:

ArrayList 和 LinkedList 各有优缺点。

ArrayList 使用连续的内存地址,而 LinkedList 使用指向下一个节点的指针。因此,当您想在 ArrayList 中查找元素时,比使用 LinkedList 进行 n 次迭代要快。

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

如果您的应用中有频繁的检索操作,请使用 ArrayList。如果您经常插入和删除,请使用 LinkedList。

【讨论】:

【参考方案27】:

ArrayList 扩展 AbstractList 并实现 List 接口。 ArrayList是动态数组。可以说它基本上是为了克服数组的弊端而创建的 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) 在数组列表中iterator.remove() 是 O(n) 而在链表中 iterator.remove() 是 O(1)

【讨论】:

【参考方案28】:

1) 底层数据结构

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

2) LinkedList 实现双端队列

ArrayList和LinkedList的另一个区别是除了List接口,LinkedList还实现了Deque接口,为add()poll()提供先进先出操作,以及其他几个Deque函数。 3)在ArrayList中添加元素 在ArrayList中添加元素如果不触发Array的re-size是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,它是一个静态嵌套类,用于存储数据和两个节点 next 和 previous,而 ArrayList 只是将数据存储在 Array 中。

因此,ArrayList 的内存需求似乎比 LinkedList 少,除了 Array 在将内容从一个 Array 复制到另一个 Array 时执行重新调整大小操作的情况。

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

从 ArrayList 与 LinkedList 之间的所有上述差异来看,看起来 ArrayList 在几乎所有情况下都是比 LinkedList 更好的选择,除非您执行频繁的add() 操作而不是remove()get()

修改链表比修改 ArrayList 更容易,尤其是当您从开头或结尾添加或删除元素时,因为链表在内部保留了对这些位置的引用,并且可以在 O(1) 时间内访问它们。

也就是说,你不需要遍历链表到达你想要添加元素的位置,这样的话,加法就变成了O(n)操作。例如,在链表中间插入或删除一个元素。

在我看来,使用 ArrayList 而不是 LinkedList 在 Java 中的大部分实际用途。

【讨论】:

我认为这是整个小组最好的答案。它准确且内容丰富。我建议更改最后一行 - 在最后添加“除了队列”,这是非常重要的结构,对于链表根本没有意义。【参考方案29】:

我在这里看到的一个测试只进行一次测试。但我注意到的是,您需要多次运行这些测试,最终它们的时间会收敛。基本上,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;

【讨论】:

【参考方案30】:

TL;DR 由于现代计算机体系结构,ArrayList 对于几乎任何可能的用例都将显着提高效率 - 因此应避免使用 LinkedList,除非一些非常独特和极端的情况.


理论上,LinkedList 对于add(E element) 有一个 O(1)

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

实践非常不同,因为 LinkedList 是一个 Cache Hostile 数据结构。从性能 POV 来看,LinkedList 的性能很少会比 缓存友好 ArrayList 更好。

以下是在随机位置插入元素的基准测试结果。如您所见 - 数组列表效率更高,尽管理论上列表中间的每个插入都需要“移动”数组的 n 个后面的元素(较低的值更好):

在下一代硬件上工作(更大、更高效的缓存)——结果更具决定性:

LinkedList 需要更多的时间来完成同样的工作。 sourceSource Code

这主要有两个原因:

    主要是 - LinkedList 的节点随机分散在内存中。 RAM(“随机存取存储器”)并不是真正随机的,需要提取内存块进行缓存。这个操作需要时间,而且当这种抓取频繁发生时——缓存中的内存页需要一直被替换->缓存未命中->缓存效率不高。 ArrayList 元素存储在连续内存中——这正是现代 CPU 架构正在优化的目标。

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

DynamicIntArray,顺便说一句,是一个自定义 ArrayList 实现,包含 Int(原始类型)而不是对象 - 因此所有数据实际上是相邻存储的 - 因此效率更高。

要记住的一个关键要素是,获取内存块的成本比访问单个内存单元的成本更重要。这就是为什么读取器 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

来源:Latency Numbers Every Programmer Should Know

为了更清楚地说明这一点,请检查将元素添加到列表开头的基准。这是一个用例,理论上,LinkedList 应该真正发光,而ArrayList 应该呈现较差甚至更坏的结果:

注意:这是 C++ 标准库的基准测试,但我之前的经验表明 C++ 和 Java 的结果非常相似。 Source Code

复制大量连续内存是现代 CPU 优化的操作 - 改变理论并实际上再次使 ArrayList/Vector 更加高效


致谢:此处发布的所有基准测试均由 Kjell Hedström 创建。更多数据可以在his blog上找到

【讨论】:

我不会称队列为独特或极端!在 LinkedList 而不是 ArrayList 上实现 fifo 队列要容易得多。这实际上是 ArrayList 的一场噩梦,因为您必须跟踪自己的开始、停止和自己的重新分配,您不妨使用数组,但链表是先进先出。我不确定 Java 的实现,但 LinkedList 可以为队列和出队操作执行 O(1) .) 插入ArrayList 数组的中间使用本地方法java.lang.System.arraycopy(),它是在OpenJDK 中用C++ 编写的。因此,虽然理论上 LinkedList 在实践中要做的工作较少,但有很多额外的语言机制使得“大 O”在很大程度上无关紧要。特别是根据这个出色的答案,缓存友好的东西是多么的好。 谢谢,但最后一个基准测试有问题。 1)为什么“列表”持续时间甚至会增长?如果元素总是在开始处插入(0 索引),则它不依赖于大小。如果你的意思是在开始时插入,那么这个“周围”的接近程度起着重要作用 - 在 Java 中,将第 1000 个元素插入到预建的 100_000 数组中(多次)对于 LinkedList 来说仍然更快,并且只有在你接近时才会变得更慢结尾。 2)所以现在Java中的环绕开始插入对于LinkedList来说仍然更快。不过,我建议在这里使用技巧 - 只需在使用它之前反转列表。

以上是关于何时在 Java 中使用 LinkedList 而不是 ArrayList?的主要内容,如果未能解决你的问题,请参考以下文章

何时在 LinkedList 或 ArrayList 上使用 HashMap,反之亦然

你啥时候使用 java.util.LinkedList [重复]

使用TreeNode而不是linkedlist实现Java 8 hashmap

Java集合框架----LinkedList源码

Java集合框架----LinkedList源码

为啥以及何时在 Spring 中使用 @Inject 而不是 @Autowired? [复制]