【问题标题】:Why is Collections.addAll supposed to be faster than c.addAll为什么 Collections.addAll 应该比 c.addAll 快
【发布时间】:2011-03-21 14:26:54
【问题描述】:

Java API docs say以下关于Collections.addAll

此便捷方法的行为与 c.addAll(Arrays.asList(elements)) 的行为相同,但此方法在大多数实现下可能运行得更快。

所以如果我理解正确的话,a) 比 b) 慢:

一)

Collection<Integer> col = new ArrayList<Integer>();
col.addAll(Arrays.asList(1, 2, 3, 4, 5));

b)

Collection<Integer> col = new ArrayList<Integer>();
// Collections.addAll(col, Arrays.asList(1, 2, 3, 4, 5)); <-- won't compile
Collections.addAll(col, 1, 2, 3, 4, 5);

谁能给我解释一下,为什么会这样?

编辑: 更正的代码示例。谢谢polygenelubricants

【问题讨论】:

    标签: java collections performance


    【解决方案1】:

    以下是@polygenelubricants 提到的每个步骤的(近似)相关时间复杂度成本函数:

    a) 对参数列表进行 3 次迭代 ~= C(3N)

    b) 对参数列表进行 2 次迭代 ~= C(2N)

    显然它们都是 O(N),但方法 b) 比方法 a) 节省了 ~N 次比较。希望这对任何对定量解释感兴趣的人有所帮助。

    【讨论】:

      【解决方案2】:

      (让我们在 SE 平台 6 上构建)

      这一切都取决于实际的集合实现。在您的示例中,我们有

      Collection<Integer> col = new ArrayList<Integer>();

      并且ArrayList 中的addAll 方法被覆盖。没有任何迭代。 这是来源:

      public boolean addAll(Collection<? extends E> c) {
      Object[] a = c.toArray();
          int numNew = a.length;
      ensureCapacity(size + numNew);  // Increments modCount
          System.arraycopy(a, 0, elementData, size, numNew);
          size += numNew;
      return numNew != 0;
      }
      

      您可能会注意到c.toArray() 也取决于实际实现。同样,在您的情况下,Arrays.asList() 导致 ArrayList 哪个版本的 toArray() 方法如下所示:

      public Object[] toArray() {
          return Arrays.copyOf(elementData, size);
      }
      

      这个静态方法基于System.arraycopy

      所以实际上我们在这里处理的是System.arraycopy 的两个调用,这实际上并没有那么糟糕,因为它是一个本地方法,专门针对当前操作系统进行了优化。

      所以,用 polygenelubricants 先生的风格总结一下:

      1. 可变参数 + 自动装箱创建 Integer[]
      2. Arrays.asList 创建一个 ArrayList&lt;Integer&gt;
      3. ArrayList.addAll 调用 System.arraycopy(size)x2,大小 = 5

      在数组Collections.addAll 中有5 个对象的情况下,当然更快。但与如此小的数组大小无关。另一方面,如果它是一个数组中的 100k 个元素,那么 col.addAll(Arrays.asList(...)) 效率更高,因为使用原生方法,它是我们处理的单个 memcpy/memmove,而不是 100k 迭代/复制操作。

      再一次,这一切都取决于集合的实现。例如LinkedList 将按预期对其进行迭代。

      【讨论】:

        【解决方案3】:

        让我们仔细看看他们两个:

        // a)
        col.addAll(Arrays.asList(1, 2, 3, 4, 5));
        

        会发生什么:

        1. 可变参数 + 自动装箱创建 Integer[]
        2. Arrays.asList 创建一个由数组支持的 List&lt;Integer&gt;
        3. addAll 使用 Iterator&lt;Integer&gt; 迭代 Collection&lt;Integer&gt;
        // b)
        Collections.addAll(col, 1, 2, 3, 4, 5);
        

        会发生什么:

        1. 可变参数 + 自动装箱创建 Integer[]
        2. addAll 遍历数组(而不是 Iterable&lt;Integer&gt;

        我们现在可以看到b) 可能更快,因为:

        • 跳过Arrays.asList 调用,即没有创建中介List
        • 由于元素是在数组中给出的(感谢可变参数机制),因此迭代它们可能比使用 Iterator 更快。

        也就是说,除非配置文件另有显示,否则差异不太可能“显着”。不要过早优化。虽然 Java 集合框架类可能比数组慢,但对于大多数应用程序来说,它们的性能已经足够了。

        API 链接

        另见

        相关问题


        总结

        • 如果要从数组中添加元素,可以使用Collections.addAll(col, arr)
          • 请记住,可变参数也是使用数组完成的
        • 如果您从Collection 添加元素,请使用col.addAll(otherCol)
          • Collections.addAll(col, otherCol.toArray())
            • 这种迂回的方式可能会比较慢!
        • 并不是说一个比另一个快得多
          • 考虑到当前情况,这是关于跳过不必要的步骤

        【讨论】:

        • 我个人认为文档在这方面有点混乱,因为我看不出该方法的实现与它有什么关系。正是该方法的使用,以及您不必在一个额外的步骤中打包数据的事实使得它更快。或者还有什么其他的方法可以让它更快?
        • 遗憾的是,(b) 中的代码仅适用于文字整数;自动装箱不会转换数组。例如,“int[] arr = {...create array...}; Collections.addAll(col, arr);”将失败,因为 Integer 和 'int' 不在继承层次结构中。
        • 使用Collections.addAll() 将数组的内容添加到HashSet 时会怎样?与for (Foo f : fooArray) { set.add(f); }相比?
        【解决方案4】:

        它可能更快的唯一原因是它避免了对 Arrays.asList 的调用,这应该相对便宜,因为它只是包装数组。一些 Collection 实现,例如 LinkedList 在添加元素之前将传递的集合转换回数组,从而导致额外的开销。

        另一方面,ArrayList.addAll 在添加任何元素之前会分配一次所需的空间,因此当 Collections.addAll 需要多次调整后备数组的大小时应该会更快。

        总之,Collections.addAll 在重复向集合中添加少量元素时可能会更快,但我怀疑这种情况是否会成为性能瓶颈。

        【讨论】:

        • 还有迭代因素,即迭代数组T[](这是Java 中的可变参数)与迭代Collection&lt;E&gt;,即通过Iterator&lt;E&gt; 迭代Iterable&lt;E&gt;
        • 对于ArrayList 的特殊情况,您始终可以先al.ensureCapacity(al.size() + array.length)。虽然是的,但可能还不足以成为一个瓶颈,人们希望将字段设为ArrayList 而不是List
        • @Carl:Effective Java 2nd Edition,Item 52:通过接口引用对象。通常不应将字段声明为 ArrayList 而不是 List
        • +1 用于指出 ArrayListLinkedList 的特定 addAll 实现怪癖。
        • @polygenelubricants:我的评论已经说明了这一点,如果可能没有那么具体的话。合法的性能问题可能是违反 EJ 特定建议的一个理由 - 我再次怀疑这里的情况。
        猜你喜欢
        • 2019-11-02
        • 2015-07-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多