我一直是一个简单使用的人:
List<String> names = new ArrayList<>();
我使用接口作为可移植性的类型名称,这样当我问类似这样的问题时,我就可以修改代码。
LinkedList何时应用于ArrayList,反之亦然?
我一直是一个简单使用的人:
List<String> names = new ArrayList<>();
我使用接口作为可移植性的类型名称,这样当我问类似这样的问题时,我就可以修改代码。
LinkedList何时应用于ArrayList,反之亦然?
当前回答
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来实现大多数实际用途。
其他回答
见原始答案下方作者的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大小,则会炸毁堆。如果你发现了这样的问题,那么无论你的解决方案是什么,都需要重新设计(我不想轻率地建议重新设计旧代码,因为我自己维护了一堆又一堆的旧代码,但这是一个很好的例子,因为原始设计已经过时,确实需要扔掉)。尽管如此,我还是会把我几十年来的糟糕观点留在那里,让你读一读。简单、合乎逻辑,而且非常错误。
链接列表的一个重要特性(我在另一个答案中没有看到)是两个列表的串联。对于数组,这是O(n)(+某些重新分配的开销),对于链接列表,这只是O(1)或O(2);-)
重要提示:对于Java的LinkedList,这不是真的!请参阅Java中是否有一种快速的链表连接方法?
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是第一种类型,即在列表末尾添加元素,然后在列表中的指定位置删除元素):
到目前为止,除了人们普遍认为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)
以下是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)
基于这些,您必须决定选择什么。:)