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

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

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

LinkedList何时应用于ArrayList,反之亦然?


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

Array vs ArrayList vs LinkedList vs Vector更深入,同样如此链接列表。


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

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


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

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

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


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

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


与LinkedList相比,Summary ArrayList和ArrayDeque在更多的用例中更可取。如果您不确定,请从ArrayList开始。


TLDR,在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的主要优点之一add(int index,E元素)为O(n)(平均步数为n/4),但当index=0或index=list.size()-1时为O(1)(在这种情况下,还可以使用addFirst()和addLast()/add())。LinkedList的主要优点之一remove(int index)为O(n)(平均步数为n/4),但当index=0或index=list.size()-1时为O(1)(在这种情况下,还可以使用removeFirst()和removeLast())。LinkedList的主要优点之一Iterator.remove()为O(1)。LinkedList的主要优点之一ListIterator.add(E元素)为O(1)。LinkedList的主要优点之一

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

对于ArrayList<E>

get(int索引)为O(1)。ArrayList的主要优势<E>add(E元素)是O(1)摊销,但O(n)最坏情况,因为数组必须调整大小并复制add(int索引,E元素)为O(n)(平均n/2步)remove(int索引)为O(n)(平均n/2步)Iterator.remove()为O(n)(平均为n/2步)ListIterator.add(E元素)为O(n)(平均n/2步)

注:许多操作平均需要n/2步,在最佳情况下(列表末尾)需要恒定的步数,在最坏情况下(开始列表)需要n步

LinkedList<E>允许使用迭代器进行恒定时间的插入或删除,但只能对元素进行顺序访问。换句话说,您可以向前或向后遍历列表,但在列表中找到位置所需的时间与列表的大小成正比。Javadoc表示“索引到列表中的操作将从开始或结束遍历列表,以较近者为准”,因此这些方法平均为O(n)(n/4步),尽管索引=0时为O(1)。

另一方面,ArrayList<E>允许快速随机读取访问,因此您可以在恒定时间内获取任何元素。但是,除了末端之外,任何地方的添加或删除都需要将后面的所有元素转换过来,要么打开,要么填补空白。此外,如果添加的元素超过了基础数组的容量,则会分配一个新数组(大小的1.5倍),并将旧数组复制到新数组,因此在最坏的情况下,添加到ArrayList是O(n),但平均来说是常量。

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

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

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

此外,如果您有大量列表,请记住内存使用情况也不同。LinkedList的每个元素都有更多的开销,因为指向下一个和上一个元素的指针也会被存储。ArrayList没有这个开销。然而,ArrayList占用的内存与为容量分配的内存一样多,而不管是否实际添加了元素。

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

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

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


ArrayList是您想要的。LinkedList几乎总是一个(性能)bug。

为什么LinkedList很糟糕:

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


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

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


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

LinkedList在性能方面几乎总是错误的选择。有一些非常具体的算法需要LinkedList,但这些算法非常非常罕见,并且该算法通常具体取决于LinkedLists在使用ListIterator导航到列表中间后相对快速地插入和删除元素的能力。

有一个常见的用例LinkedList优于ArrayList:队列。但是,如果您的目标是性能,那么您也应该考虑使用ArrayBlockingQueue(如果您可以提前确定队列大小的上限,并且能够提前分配所有内存),而不是LinkedList,或者使用CircularArray实现。(是的,它来自2001年,因此您需要对其进行一般化,但我得到的性能比与最近一篇JVM文章中引用的性能比相当)


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

重要提示:对于Java的LinkedList,这不是真的!请参阅Java中是否有一种快速的链表连接方法?


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适用于一次写入、多次读取或追加,但不适用于从前面或中间添加/删除。


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

链接列表指定从一个项目到下一个项目(项目A->项目b)的进度。您可以使用数组列表获得相同的效果,但链接列表绝对会说明前一个列表后面应该包含哪些项。


见原始答案下方作者的2021更新。


原答案(2011年)

作为一个在非常大规模的SOA web服务上做了大约十年操作性能工程的人,我更喜欢LinkedList而不是ArrayList的行为。虽然LinkedList的稳态吞吐量更差,因此可能会导致购买更多硬件,但ArrayList在压力下的行为可能会导致集群中的应用程序以近乎同步的方式扩展其阵列,而对于较大的阵列大小,可能会导致应用程序缺乏响应能力,在压力下停机,这是灾难性的行为。

类似地,您可以从默认的吞吐量固定垃圾收集器中获得更好的应用吞吐量,但一旦您获得了具有10GB堆的java应用程序,您就可以在完全GC期间锁定应用程序25秒,这会导致SOA应用程序超时和失败,如果太频繁,还会破坏SLA。尽管CMS收集器占用了更多的资源,并且没有实现相同的原始吞吐量,但它是一个更好的选择,因为它具有更可预测性和更小的延迟。

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

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

这个答案(也是我在SO问题上最受欢迎的答案)很可能是错误的(原因在下面的评论中概述)。我想补充一点,ArrayList将优化内存的顺序读取,并最小化缓存线和TLB未命中等。相比之下,当阵列增长超过边界时的复制开销可能无关紧要(可以通过高效的CPU操作完成)。考虑到硬件趋势,随着时间的推移,这个答案可能会变得更糟。LinkedList可能有意义的唯一情况是,如果您有数千个列表,其中任何一个都可能增长到GB大小,但在分配列表时无法做出正确的猜测,并且将它们全部设置为GB大小,则会炸毁堆。如果你发现了这样的问题,那么无论你的解决方案是什么,都需要重新设计(我不想轻率地建议重新设计旧代码,因为我自己维护了一堆又一堆的旧代码,但这是一个很好的例子,因为原始设计已经过时,确实需要扔掉)。尽管如此,我还是会把我几十年来的糟糕观点留在那里,让你读一读。简单、合乎逻辑,而且非常错误。


请参阅Java教程-列表实现。


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

LinkedList中的编辑/删除速度比ArrayList快。

Array支持的ArrayList需要两倍的大小,在大容量应用程序中更糟糕。

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


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,我想分享它来听取意见:

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

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

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


到目前为止,除了人们普遍认为LinkedList比ArrayList“多得多”之外,似乎没有人解决这些列表中每一个的内存占用问题,所以我做了一些数字处理,以证明这两个列表对于N个空引用所占的空间。

由于引用在其相对系统上是32位或64位(即使为空),因此我为32位和64位LinkedList和ArrayList包含了4组数据。

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

注2:(感谢BeeOnRope)由于压缩Oops现在是默认值,从JDK6中期开始,以下64位机器的值将基本上与32位机器的对应值相匹配,当然,除非您特意关闭它。



结果清楚地表明,LinkedList比ArrayList多得多,尤其是元素数非常高的情况。如果内存是一个因素,请避开LinkedList。

我使用的公式如下,如果我做错了什么,请告诉我,我会改正的对于32位或64位系统,b’是4或8,而n’是元素的数量。注意mods的原因是因为java中的所有对象都将占用8字节的倍数空间,而不管是否全部使用。

阵列列表:

ArrayList对象头+大小整数+modCount整数+数组引用+(数组项目头+b*n)+MOD

链接列表:

LinkedList对象标头+大小整数+modCount整数+对标头的引用+对页脚的引用+(节点对象开销+对上一元素的引用+下一元素的参考+对元素的引用)*n)+MOD(节点对象,8)*n+MOD, 8)


我应该何时使用LinkedList?大多数情况下使用堆栈时,或使用缓冲区时。我应该何时使用ArrayList?只有在使用索引时,否则您可以将HashTable与链接列表一起使用,那么您将得到:

哈希表+链接列表

通过密钥O(1)访问,通过键O(1)插入,通过键O(1)拆除在使用版本控制时,使用O(1)实现RemoveAll/SetAll有一个技巧

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

还可以看看红黑树

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


以下是ArrayList和LinkedList以及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阵列列表

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实现为双链接列表。

答案很清楚。O(1)表示ArrayList,因为ArrayList允许使用索引进行随机访问。O(n)表示LinkedList,因为它需要首先查找索引。注意:添加和删除有不同的版本。

LinkedList在添加和删除时速度更快,但在获取时速度较慢。简而言之,在以下情况下,应首选LinkedList:

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

==阵列列表===

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

==链接列表===

添加(E E)添加到列表末尾O(1)add(int索引,E元素)在指定位置插入需要先找到位置O(n)删除()删除列表的第一个元素O(1)删除(int索引)删除具有指定索引的元素需要先找到元素O(n)删除(对象o)删除指定元素的第一个引用需要先找到元素O(n)

这是programcreek.com中的一个图(add和remove是第一种类型,即在列表末尾添加元素,然后在列表中的指定位置删除元素):


ArrayList中的get(i)操作比LinkedList更快,因为:ArrayList:List接口的可调整大小的数组实现LinkedList:list和Deque接口的双重链接列表实现

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


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

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

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

插入和删除这两项中的哪一项更快取决于发生的位置。如果我们更接近开始,LinkedList将更快,因为我们必须经过相对较少的元素。如果我们接近末尾,ArrayList将更快,因为我们在恒定的时间内到达那里,只需更改紧随其后的几个剩余元素。如果正好在中间完成,LinkedList将更快速,因为遍历n个元素比移动n个值更快。

好处:虽然无法为ArrayList创建这两个方法O(1),但实际上在LinkedList中有一种方法可以做到这一点。假设我们想在整个列表中删除和插入元素。通常,您可以使用LinkedList从头开始每个元素,我们也可以使用迭代器“保存”当前正在处理的元素。在迭代器的帮助下,当在LinkedList中工作时,remove()和insert()的效率为O(1)。使其成为我所知的唯一性能优势,LinkedList总是优于ArrayList。


ArrayList和LinkedList都实现了List接口,它们的方法和结果几乎相同。然而,它们之间几乎没有区别,这取决于需求,使一个优于另一个。

阵列列表与链接列表

1) 搜索:与LinkedList搜索操作相比,ArrayList搜索操作非常快。ArrayList中的get(int index)给出了O(1)的性能,而LinkedList的性能为O(n)。

原因:ArrayList为其元素维护基于索引的系统,因为它隐式使用数组数据结构,这使得在列表中搜索元素的速度更快。另一方面,LinkedList实现了双链接列表,这需要遍历所有元素来搜索元素。

2) 删除:LinkedList删除操作提供O(1)性能,而ArrayList提供可变性能:最坏情况下(删除第一个元素时)为O(n),最好情况下(移除最后一个元素时,为O(2)。

结论:LinkedList元素删除速度比阵列列表。

原因:LinkedList的每个元素都有两个指针(地址),指向列表中的两个相邻元素。因此,移除仅需要改变将要移除的节点的两个相邻节点(元素)中的指针位置。当在ArrayList中时,需要移动所有元素以填充移除的元素所创建的空间。

3) 插入性能:LinkedList add方法提供O(1)性能,而ArrayList在最坏情况下提供O(n)性能。原因与删除说明相同。

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

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

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

ArrayList和LinkedList都是List接口的实现。它们都保持元素插入顺序,这意味着在显示ArrayList和LinkedList元素时,结果集将具有元素插入列表的相同顺序。这两个类都是非同步的,可以使用Collections.synchronizedList方法显式同步。这些类返回的迭代器和listIterator是快速失败的(如果在创建迭代器之后的任何时候对列表进行结构修改,除了通过迭代器自己的remove或add方法之外,其他任何方式,迭代器都会抛出ConcurrentModificationException)。

何时使用LinkedList,何时使用ArrayList?

如上所述,与ArrayList(O(n))相比,插入和删除操作在LinkedList中提供了良好的性能(O(1))。因此,若应用程序中需要频繁添加和删除,则LinkedList是最佳选择。搜索(get方法)操作在Arraylist(O(1))中很快,但在LinkedList(O(n))中不快因此,如果添加和删除操作更少,搜索操作需求更多,ArrayList将是您的最佳选择。


让我们将LinkedList和ArrayList与以下参数进行比较:

1.实施

ArrayList是列表接口的可调整大小的数组实现,而LinkedList是列表接口的双重链接列表实现。


2.性能

get(int索引)或搜索操作ArrayList get(int索引)操作在恒定时间内运行,即O(1)而LinkedList get(int索引)操作运行时间为O(n)。ArrayList比LinkedList更快的原因是ArrayList对其元素使用基于索引的系统,LinkedList不为其元素提供基于索引的访问,因为它从开始或结束(以较近者为准)迭代以检索指定元素索引处的节点。insert()或add(Object)操作与ArrayList相比,LinkedList中的插入通常很快。在LinkedList中,添加或插入是O(1)操作。在ArrayList中,如果数组已满(即最坏情况),则调整数组大小并将元素复制到新数组会产生额外的成本,这使得ArrayList的加法运算运行时为O(n),否则为O(1)。删除(int)操作LinkedList中的移除操作通常与ArrayList相同,即O(n)。在LinkedList中,有两个重载的移除方法。一个是remove(),没有任何参数,它会删除列表的头部,并在恒定时间O(1)内运行。LinkedList中的另一个重载remove方法是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.内存开销

与ArrayList相比,LinkedList中的内存开销更大,因为LinkedList的节点需要维护下一个和上一个节点的地址。虽然在ArrayList中,每个索引仅保存实际对象(数据)。


来源


Joshua Bloch,LinkedList的作者:

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

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

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


ArrayList和LinkedList各有利弊。

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

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

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


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


1) 基础数据结构

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

2) LinkedList实现Deque

ArrayList和LinkedList之间的另一个区别是,除了List接口之外,LinkedList还实现了Deque接口,该接口为add()和poll()以及其他几个Deque函数提供先进先出操作。3) 在ArrayList中添加元素如果不触发Array的重新调整大小,则在ArrayList中添加元素是O(1)操作,在这种情况下,它变为O(log(n))。另一方面,在LinkedList中添加一个元素则是O(2)操作,因为它不需要任何导航。

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中存储数据。

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

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

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

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

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

在我看来,在Java中,使用ArrayList而不是LinkedList来实现大多数实际用途。


我在这里看到的一个测试只进行一次测试。但我注意到的是,您需要多次运行这些测试,最终它们的时间会收敛。基本上JVM需要预热。对于我的特定用例,我需要向列表中添加/删除项目,该列表将增加到大约500个项目。在我的测试中,LinkedList的发布速度更快,LinkedList约为50000 NS,ArrayList约为90000NS。。。给予或索取。请参见下面的代码。

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

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


理论上,LinkedList的add(E元素)有一个O(1)

此外,在列表中间添加元素应该非常有效。

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

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

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

LinkedList需要更多的时间来完成相同的任务。源源代码

这主要有两个原因:

主要是LinkedList的节点在内存中随机分布。RAM(“随机存取存储器”)不是真正随机的,需要将内存块提取到缓存中。此操作需要时间,并且当此类提取频繁发生时,缓存中的内存页需要一直被替换->缓存未命中->缓存效率不高。ArrayList元素存储在连续内存中——这正是现代CPU架构正在优化的目标。Secondary LinkedList需要保留/转发指针,这意味着与ArrayList相比,每个存储值的内存消耗是3倍。

顺便说一句,DynamicIntArray是一个自定义ArrayList实现,它保存Int(原始类型)而不是Object,因此所有数据都是相邻存储的,因此效率更高。

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

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应该呈现出糟糕甚至更糟糕的用例结果:

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

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


致谢:这里发布的所有基准都是由Kjell Hedström创建的。在他的博客上可以找到更多的数据


您可以根据对该特定列表执行的操作的时间复杂性,使用一个而不是另一个。

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

我的经验法则是,如果我需要一个集合(即不需要是一个列表),那么如果你事先知道大小,或者可以自信地知道大小,或知道它不会有太大变化,那么就使用ArrayList。如果您需要随机访问(即使用get(index)),请避免LinkedList。基本上,只有当您不需要索引访问并且不知道正在分配的集合的(近似)大小时,才使用LinkedList。此外,如果您要进行大量添加和删除(再次通过Collection接口),则LinkedList可能更可取。


首先使用Vector而不是ArrayList,因为您可以覆盖insuranceCapasity方法,在ArrayList中是私有的,并添加1.5大小的当前数组https://docs.oracle.com/javase/8/docs/api/java/util/Vector.html#ensureCapacity-整数-

在很多情况下,linkedList会更好,las有一个很大的优势:你可以高频插入数据,所以列表的大小变化很快,你不能为数字元素分配大小。从理论上讲,你可能会遇到“内存不足”之类的错误,但在现代计算机中,你有16G和交换磁盘,所以如果你列出的是billoins元素,与15-20年前相比,你可能失败。