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

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

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

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


当前回答

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

其他回答

见原始答案下方作者的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大小,则会炸毁堆。如果你发现了这样的问题,那么无论你的解决方案是什么,都需要重新设计(我不想轻率地建议重新设计旧代码,因为我自己维护了一堆又一堆的旧代码,但这是一个很好的例子,因为原始设计已经过时,确实需要扔掉)。尽管如此,我还是会把我几十年来的糟糕观点留在那里,让你读一读。简单、合乎逻辑,而且非常错误。

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将是您的最佳选择。

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

ArrayList和LinkedList各有利弊。

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

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

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

以下是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)

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