【问题标题】:Why is ArrayDeque better than LinkedList为什么 ArrayDeque 比 LinkedList 好
【发布时间】:2011-09-04 00:35:54
【问题描述】:

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

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

如果我理解它,我会更有信心使用它。我无法清楚地理解 JDK 实现管理头尾引用的方式。

【问题讨论】:

  • 看我几天前做的这个问题的答案:stackoverflow.com/questions/6129805/…
  • 在您安装 jdk 的文件夹中,有一个文件 src.zip。它是带有 java 类源代码的存档。我强烈建议研究这些类的结构和内部结构,以更好地了解 Java 类的工作原理。
  • 还有一点。 ArrayDeque 的默认大小是 16。当 ArrayDeque 满时,它的大小会翻倍。大小翻倍后,元素被复制到新数组中。最好使用初始大小来初始化 ArrayDeque。
  • 还有一点值得一提的是,在 LinkedList 上,您可以使用索引来遍历其元素,而 ArrayDeque 不支持基于索引的访问。

标签: java deque linked-list 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 实现了 ListArrayDeque 没有。这意味着LinkedList 具有indexOfremove(int) 之类的方法,而ArrayDeque 没有。有时它可能很重要。
    【解决方案3】:

    虽然ArrayDeque<E>LinkedList<E>都实现了Deque<E>接口,但是ArrayDeque基本使用Object数组 E[] 用于将元素保留在其对象中,因此它通常使用索引来定位头尾元素。

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

    【讨论】:

      【解决方案4】:

      我认为LinkedList 中的主要性能瓶颈是这样一个事实,即每当您推送到双端队列的任何一端时,该实现都会在后台分配一个新的链表节点,这本质上涉及 JVM/OS,而且成本很高.此外,无论何时从任何一端弹出,LinkedList 的内部节点都可以进行垃圾收集,这在幕后需要更多的工作。 另外,由于链表节点被分配在这里和那里,CPU缓存的使用不会提供太多好处。

      如果可能感兴趣,我有一个证明,证明向ArrayListArrayDeque 添加(附加)一个元素在摊销常数时间内运行;参考this

      【讨论】:

      • 你能告诉我如何在 Linked 中从头部添加/删除元素比在 Array 中更好吗? LinkedList 不应该有优势,因为只更改了对 prevnext 的引用,而在 ArrayDeque 中需要移动许多元素?
      【解决方案5】:

      ArrayDequeLinkedList 正在实现 Deque 接口,但实现不同。

      主要区别:

      1. ArrayDeque 类是 Deque 接口的可调整大小的数组实现,LinkedList 类是列表实现

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

      3. ArrayDequeLinkedList 更有效地进行两端的添加和删除操作,LinkedList 实现在迭代期间删除当前元素的效率更高 p>

      4. LinkedList 实现比 ArrayDeque

      5. 消耗更多内存

      所以如果你不必支持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 的人可能大部分时间都使用ArrayListLinkedList,因为他们在Java 6 之前就已经使用了,因为那些是在大多数书籍中作为开始教授的内容。

      但是,这并不意味着,我会盲目地站在LinkedListArrayDeque 一边。如果您想了解,请查看以下基准 done by Brian(已存档)。

      测试设置考虑:

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

      测试结果:

      • 低于 10,000 个元素,LinkedList 和 ArrayDeque 测试的平均时间都在 1 毫秒以下。
      • 随着数据集越来越大,ArrayDeque 和 LinkedList 平均测试时间的差异越来越大。
      • 在 9,900,000 个元素的测试大小下,LinkedList 方法比 ArrayDeque 方法花费的时间长约 165%。

      图表:

      外卖:

      • 如果您的要求是存储 100 或 200 个元素,则不会 使用任何一个队列都有很大的不同。
      • 但是,如果您在移动设备上进行开发,您可能希望使用 ArrayListArrayDeque 猜测最大容量 由于严格的内存限制,可能需要该列表。
      • 存在大量代码,使用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】:

        我不认为ArrayDequeLinkedList 更好。它们是不同的。

        ArrayDeque 平均比LinkedList 快。但是对于添加元素,ArrayDeque 需要分摊的常数时间,LinkedList 需要常数时间。

        对于需要所有操作都花费恒定时间的时间敏感型应用程序,应该只使用LinkedList

        ArrayDeque 的实现使用数组,需要调整大小,偶尔当数组满了需要添加元素时,调整大小需要线性时间,导致add() 方法需要线性时间。如果应用程序对时间非常敏感,那可能是一场灾难。

        有关 Java 实现这两种数据结构的更详细说明,请参阅由 Wayne 和 Sedgewick 教授的普林斯顿大学提供的 Coursera 上的“Algorithms, Part I”课程。该课程免费向公众开放。

        详细信息在“第 2 周”的“堆栈和队列”部分的视频“调整数组大小”中进行了说明。

        【讨论】:

          猜你喜欢
          • 2011-08-24
          • 1970-01-01
          • 2021-12-19
          • 2023-04-01
          • 2012-04-09
          • 1970-01-01
          • 2020-04-27
          • 2011-01-04
          • 2017-06-30
          相关资源
          最近更新 更多