为啥 ArrayDeque 比 LinkedList 好
Posted
技术标签:
【中文标题】为啥 ArrayDeque 比 LinkedList 好【英文标题】:Why is ArrayDeque better than LinkedList为什么 ArrayDeque 比 LinkedList 好 【发布时间】:2011-09-04 00:35:54 【问题描述】:我试图理解为什么 Java 的 ArrayDeque 比 Java 的 LinkedList 更好,因为它们都实现了 Deque 接口。
我几乎看不到有人在他们的代码中使用 ArrayDeque。如果有人对 ArrayDeque 的实现方式有更多的了解,那将会很有帮助。
如果我理解它,我会更有信心使用它。我无法清楚地理解 JDK 实现管理头尾引用的方式。
【问题讨论】:
看我几天前做的这个问题的答案:***.com/questions/6129805/… 在您安装 jdk 的文件夹中,有一个文件src.zip
。它是带有 java 类源代码的存档。我强烈建议研究这些类的结构和内部结构,以更好地了解 Java 类的工作原理。
还有一点。 ArrayDeque 的默认大小是 16。当 ArrayDeque 满时,它的大小会翻倍。大小翻倍后,元素被复制到新数组中。最好使用初始大小来初始化 ArrayDeque。
还有一点值得一提的是,在 LinkedList 上,您可以使用索引来遍历其元素,而 ArrayDeque 不支持基于索引的访问。
【参考方案1】:
ArrayDeque
是 Java 6 的新功能,这就是为什么很多代码(尤其是试图与早期 Java 版本兼容的项目)不使用它的原因。
在某些情况下它“更好”,因为您没有为要插入的每个项目分配一个节点;取而代之的是,所有元素都存储在一个巨大的数组中,如果它满了就会调整大小。
【讨论】:
【参考方案2】:链接结构可能是最糟糕的迭代结构,每个元素都会出现缓存未命中。最重要的是,它们会消耗更多的内存。
如果您需要添加/删除两端,ArrayDeque 明显优于链表。对于循环队列,随机访问每个元素也是 O(1)。
链表唯一更好的操作是在迭代期间删除当前元素。
【讨论】:
要记住的另一个区别:LinkedList 支持空元素,而 ArrayDeque 不支持。 另外一个小缺点(对于实时应用程序)是,在推送/添加操作中,当 ArrayDeque 的内部数组已满时,它需要更多的时间,因为它必须加倍它的大小和复制所有数据。 @AndreiI,这只是故事的一方面。即使您排除了实时应用程序的迭代成本和预分配所需容量的能力,GC 也可能需要迭代整个 LinkedList。基本上,您正在将成本(启动成本更高)转移到 GC 中。 @DavidT。 b / c它涉及释放节点的GC成本,分配头部可能还需要卡标记(再次为GC,如果LinkedList已经在tenured gen中)......而且这是额外的间接(缓存- miss) 返回元素并重新链接。 另外,LinkedList
实现了 List
而ArrayDeque
没有。这意味着LinkedList
具有indexOf
或remove(int)
之类的方法,而ArrayDeque
没有。有时它可能很重要。【参考方案3】:
虽然ArrayDeque<E>
和LinkedList<E>
都实现了Deque<E>
接口,但是ArrayDeque基本使用Object数组 E[]
用于将元素保留在其对象中,因此它通常使用索引来定位头尾元素。
总之,它就像 Deque 一样工作(使用所有 Deque 的方法),但是使用数组的数据结构。至于哪个更好,取决于您使用它们的方式和位置。
【讨论】:
【参考方案4】:我认为LinkedList
中的主要性能瓶颈是这样一个事实,即每当您推送到双端队列的任何一端时,该实现都会在后台分配一个新的链表节点,这本质上涉及 JVM/OS,而且成本很高.此外,无论何时从任何一端弹出,LinkedList
的内部节点都可以进行垃圾收集,这在幕后需要更多的工作。
另外,由于链表节点被分配在这里和那里,CPU缓存的使用不会提供太多好处。
如果可能感兴趣,我有一个证明,证明向ArrayList
或ArrayDeque
添加(附加)一个元素在摊销常数时间内运行;参考this。
【讨论】:
你能告诉我如何在 Linked 中从头部添加/删除元素比在 Array 中更好吗? LinkedList 不应该有优势,因为只更改了对prev
和 next
的引用,而在 ArrayDeque 中需要移动许多元素?【参考方案5】:
ArrayDeque 和 LinkedList 正在实现 Deque 接口,但实现不同。
主要区别:
ArrayDeque 类是 Deque 接口的可调整大小的数组实现,LinkedList 类是列表实现
NULL 元素可以添加到 LinkedList 但不能添加到 ArrayDeque
ArrayDeque 比 LinkedList 更有效地进行两端的添加和删除操作,LinkedList 实现在迭代期间删除当前元素的效率更高 p>
LinkedList 实现比 ArrayDeque
消耗更多内存所以如果你不必支持NULL元素&&寻找更少的内存&&两端添加/删除元素的效率,ArrayDeque是最好的
详情请参阅documentation。
【讨论】:
"在链表中,需要 O(N) 才能找到最后一个元素。"不完全正确。 LinkedList 实现为双向链表,因此您不必遍历列表即可获取最后一个元素 (header.previous.element
)。 “内存效率”的说法也可能会受到挑战,因为支持数组的大小总是被调整为 2 的下一个幂。
“找到最后一个元素需要 O(N)”是错误的。链表保留对最后一个节点的引用,LinkedList.descendingIterator() 获取该节点。所以我们得到 O(1) 的性能。请参阅:coffeeorientedprogramming.wordpress.com/2018/04/23/…(因此投反对票)。
当您使用Iterator
访问最后一个元素时,两个类的操作都是 O(N)。当您使用常见的Deque
接口时,访问最后一个元素对于两个类都是 O(1)。无论您采取哪种观点,同时将 O(1) 归因于 ArrayDeque
和 O(N) 归因于 LinkedList
,都是错误的。
您的所有建议都被采纳并更正了内容【参考方案6】:
ArrayDeque 访问一个元素的时间复杂度是 O(1),而 LinkList 访问最后一个元素的时间复杂度是 O(N)。 ArrayDeque 不是线程安全的,因此需要手动同步,以便您可以通过多个线程访问它,因此它们更快。
【讨论】:
如果你指的是Java的Collection
中的LinkedList
,它是双向链接的,并且可以快速访问头部和尾部,因此访问最后一个元素也需要O(1)。
访问 LinkedList 中的最后一个元素不是 O(N)。如果你使用 descendingIterator(),它在 O(1) 中执行。请参阅coffeeorientedprogramming.wordpress.com/2018/04/23/…(因此投反对票)。
这两个类都不是线程安全的。并且句子的开头“必须手动同步”和结尾的“他们更快”之间没有联系。【参考方案7】:
所有批评LinkedList
的人,想想其他在Java 中使用List
的人可能大部分时间都使用ArrayList
和LinkedList
,因为他们在Java 6 之前就已经使用了,因为那些是在大多数书籍中作为开始教授的内容。
但是,这并不意味着,我会盲目地站在LinkedList
或ArrayDeque
一边。如果您想了解,请查看以下基准 done by Brian(已存档)。
测试设置考虑:
每个测试对象都是一个 500 个字符的字符串。每个字符串都是内存中的不同对象。 测试数组的大小将在测试期间发生变化。 对于每个数组大小/队列实现组合,运行 100 次测试并计算每次测试的平均时间。 每个测试都包括用所有对象填充每个队列,然后将它们全部删除。 以毫秒为单位测量时间。
测试结果:
低于 10,000 个元素,LinkedList 和 ArrayDeque 测试的平均时间都在 1 毫秒以下。 随着数据集越来越大,ArrayDeque 和 LinkedList 平均测试时间的差异越来越大。 在 9,900,000 个元素的测试大小下,LinkedList 方法比 ArrayDeque 方法花费的时间长约 165%。
图表:
外卖:
如果您的要求是存储 100 或 200 个元素,则不会 使用任何一个队列都有很大的不同。 但是,如果您在移动设备上进行开发,您可能希望使用ArrayList
或 ArrayDeque
猜测最大容量
由于严格的内存限制,可能需要该列表。
存在大量代码,使用LinkedList
编写,因此在决定使用ArrayDeque
时要小心谨慎,尤其是因为它没有实现List
接口(我认为这是原因足够大)。可能是您的代码库与 List 接口进行了广泛的对话,很可能您决定使用ArrayDeque
。将其用于内部实现可能是个好主意...
【讨论】:
这个benchmark如何捕捉链表垃圾造成的GC时间?【参考方案8】:情况并非总是如此。
例如,根据 leetcode 103,linkedlist
的性能优于 ArrayDeque
。
/**
* Definition for a binary tree node.
* public class TreeNode
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) val = x;
*
*/
class Solution
public List<List<Integer>> zigzagLevelOrder(TreeNode root)
List<List<Integer>> rs=new ArrayList<>();
if(root==null)
return rs;
// ? here ,linkedlist works better
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
boolean left2right=true;
while(!queue.isEmpty())
int size=queue.size();
LinkedList<Integer> t=new LinkedList<>();
while(size-->0)
TreeNode tree=queue.remove();
if(left2right)
t.add(tree.val);
else
t.addFirst(tree.val);
if(tree.left!=null)
queue.add(tree.left);
if(tree.right!=null)
queue.add(tree.right);
rs.add(t);
left2right=!left2right;
return rs;
【讨论】:
【参考方案9】:我不认为ArrayDeque
比LinkedList
更好。它们是不同的。
ArrayDeque
平均比LinkedList
快。但是对于添加元素,ArrayDeque
需要分摊的常数时间,LinkedList
需要常数时间。
对于需要所有操作都花费恒定时间的时间敏感型应用程序,应该只使用LinkedList
。
ArrayDeque
的实现使用数组,需要调整大小,偶尔当数组满了需要添加元素时,调整大小需要线性时间,导致add()
方法需要线性时间。如果应用程序对时间非常敏感,那可能是一场灾难。
有关 Java 实现这两种数据结构的更详细说明,请参阅由 Wayne 和 Sedgewick 教授的普林斯顿大学提供的 Coursera 上的“Algorithms, Part I”课程。该课程免费向公众开放。
详细信息在“第 2 周”的“堆栈和队列”部分的视频“调整数组大小”中进行了说明。
【讨论】:
以上是关于为啥 ArrayDeque 比 LinkedList 好的主要内容,如果未能解决你的问题,请参考以下文章