为什么ArrayDeque比LinkedList更好


161

我试图理解为什么Java的ArrayDeque比Java的LinkedList更好,因为它们都实现了Deque接口。

我几乎看不到有人在他们的代码中使用ArrayDeque。如果有人对ArrayDeque的实现方式有了更多的了解,那将是有帮助的。

如果我理解它,我会更自信地使用它。对于JDK实现管理头和尾引用的方式,我不清楚。


5
看看这个问题,我的答案做天前:stackoverflow.com/questions/6129805/...
雷纳托Dinhani

1
在安装了jdk的文件夹中,有一个文件src.zip。它是带有Java类源代码的存档。我强烈建议研究这些类的结构和内部结构,以更好地理解Java类的工作方式。

Answers:


155

链接结构可能是最糟糕的结构,要在每个元素上进行缓存未命中来进行迭代。最重要的是,它们消耗了更多的内存。

如果您需要两端的添加/删除,则ArrayDeque明显优于链接列表。对于循环队列,每个元素的随机访问也是O(1)。

链表的唯一更好的操作是在迭代过程中删除当前元素。


57
要记住的另一个区别:LinkedList支持null元素,而ArrayDeque不支持。
路加·乌舍伍德

15
另一个小缺点(对于实时应用程序)是,在推入/添加操作中,如果ArrayDeque的内部数组已满,则需要花费更多时间,因为它必须加倍其大小并复制所有数据。
Andrei I

4
@AndreiI,这只是故事的一个方面。即使您排除了实时应用程序的迭代成本和预分配所需容量的能力,GC也可能需要迭代整个LinkedList。基本上,您正在将成本(启动成本较高)转移到GC中。
2014年

3
@DavidT。b / c涉及释放节点的GC成本,分配磁头可能还需要标记卡(对于LinkedList,如果LinkedList已经在使用权的一代中,则再次对GC进行标记)...而且这是额外间接寻址(缓存-错过)返回元素并重新链接。
2014年

1
另外,LinkedList实现ListArrayDeque没有。这意味着,LinkedList有方法,如indexOfremove(int)同时ArrayDeque也没有。有时可能很重要。
ZhekaKozlov

60

我认为主要的性能瓶颈LinkedList在于,无论何时您将双端队列移到后台的任何一端,该实现都会在后台分配一个新的链表节点,这实际上涉及JVM / OS,这很昂贵。而且,无论何时从任何一端弹出,的内部节点都有LinkedList资格进行垃圾收集,而这背后的工作更多。另外,由于链表节点是在各处分配的,因此使用CPU缓存不会带来太多好处。

如果有兴趣,我有证据表明,将元素添加(附加)到摊销的固定时间内ArrayListArrayDeque在摊销的固定时间内运行;参考这个


26

ArrayDeque 是Java 6的新增功能,这就是为什么很多代码(尤其是试图与早期Java版本兼容的项目)不使用它的原因。

在某些情况下,它“更好”,因为您没有为要插入的每个项目分配节点。取而代之的是,所有元素都存储在一个巨大的数组中,如果数组已满,则会对其进行调整大小。


17

所有批评a的人LinkedList都会想到List在Java中使用过的其他每个人, ArrayList而且在LinkedList大多数情况下,是因为他们早于Java 6,并且因为这些是大多数书籍中开始学习的东西。

但这并不意味着,我会盲目站在LinkedListArrayDeque的一边。如果您想知道,请看Brian所做的以下基准测试。

测试设置考虑:

  • 每个测试对象都是一个500个字符的字符串。每个字符串是内存中的不同对象。
  • 在测试期间,测试阵列的大小将有所不同。
  • 对于每个阵列大小/队列实现组合,将运行100个测试,并计算平均每次测试时间。
  • 每个测试包括用所有对象填充每个队列,然后将其全部删除。
  • 以毫秒为单位测量时间。

测试结果:

  • 低于10,000个元素时,LinkedList和ArrayDeque测试的平均值均在1 ms以下。
  • 随着数据集变大,ArrayDeque和LinkedList平均测试时间之间的差异也变大。
  • 在990万个元素的测试大小下,LinkedList方法比ArrayDeque方法花费了约165%的时间。

图形:

在此处输入图片说明

带走:

  • 如果您的需求是存储100或200个元素,则使用两个队列都不会有太大的区别。
  • 但是,如果您在移动设备上进行开发,则可能由于严格的内存限制而希望使用 ArrayListArrayDeque很好地估计可能需要该列表的最大容量。
  • 存在很多代码,LinkedList在决定使用ArrayDeque特别代码时要谨慎地使用代码编写,因为它没有实现List接口(我认为这足够大)。可能是您的代码库与List接口进行了广泛的对话,最有可能的是,您决定跳入ArrayDeque。将其用于内部实现可能是一个好主意...

此基准将如何捕获由链表垃圾引起的GC时间?
0xbe5077ed

3

ArrayDeque LinkedList正在实现Deque接口,但实现方式有所不同。

主要区别:

  1. 所述ArrayDeque类是的可调整大小的数组实现的Deque接口和链表类是列表实现

  2. 可以将NULL元素添加到LinkedList,但不能添加到ArrayDeque中

  3. ArrayDequeLinkedList更有效对于两端的添加和删除操作,,LinkedList实现对于在迭代过程中删除当前元素非常有效。

  4. 链表实现消耗比更多的内存ArrayDeque

因此,如果您不必支持NULL元素&&寻找较少的内存,并且&&在两端添加/删除元素的效率都很高, ArrayDeque是最好的

有关更多详细信息,请参考文档


13
“在链接列表中,将需要O(N)来找到最后一个元素。” 是不完全正确的。LinkedList被实现为双向链接列表,因此您不必遍历列表即可获取最后一个元素(header.previous.element)。由于后备阵列始终会调整为下一个2的幂,因此“内存效率”声明也可能会受到挑战。
ClémentMATHIEU 2015年

5
“需要O(N)查找最后一个元素”是错误的。链接列表保留对最后一个节点的引用,而LinkedList.descendingIterator()获取该节点。这样我们得到O(1)性能。参见:coffeedirectionalprogramming.wordpress.com/2018/04/23/…(因此投票)。
Leo Ufimtsev '18

1
使用an Iterator访问最后一个元素时,两个类的操作均为O(N)。使用公共Deque接口时,两个类的最后一个访问元素都是O(1)。无论您采用哪种观点,将O(1)归因于ArrayDequeO(N)LinkedList都是错误的。
Holger

您所有的建议都会被采纳并更正内容
Ravindra babu

1

尽管ArrayDeque<E>并且LinkedList<E>都实现了 Deque<E>Interface,但是ArrayDeque基本上使用Object数组E[]将元素保留在其Object中,因此通常使用index来定位头和尾元素。

简而言之,它就像Deque一样(使用所有Deque的方法),但是使用数组的数据结构。至于哪种更好,取决于您使用它们的方式和位置。


-1

并非总是如此。

例如,在以下情况下,linkedlist其性能要比ArrayDeque根据leetcode 103 更好。

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

-5

ArrayDeque用于访问元素的时间复杂度为O(1),而LinkList的时间复杂度为O(N)以访问最后一个元素。ArrayDeque不是线程安全的,因此需要手动同步,以便您可以通过多个线程访问它,因此它们更快。


3
如果您LinkedList在Java 中引用Collection,则它是双向链接的,并且可以快速访问头部和尾部,因此访问最后一个元素也需要O(1)。
莫里斯

访问LinkedList中的最后一个元素不是O(N)。如果使用下降的Iterator(),则在O(1)中执行。见coffee取向的programming.wordpress.com/2018/04/23/… (因此请向下投票)。
Leo Ufimtsev '18

1
这两个类都不是线程安全的。而且,在“手动同步是必要的”一词的开头和“它们更快”的结尾之间没有联系。
Holger
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.