【问题标题】:What is difference between Collection.stream().forEach() and Collection.forEach()?Collection.stream().forEach() 和 Collection.forEach() 有什么区别?
【发布时间】:2014-06-06 18:51:05
【问题描述】:

我了解使用.stream(),我可以使用.filter() 之类的链式操作或使用并行流。但是如果我需要执行小操作(例如,打印列表的元素),它们之间有什么区别?

collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);

【问题讨论】:

    标签: java collections java-8 java-stream


    【解决方案1】:

    您提到的两者之间没有区别,至少在概念上,Collection.forEach() 只是一个简写。

    在内部,stream() 版本由于对象创建而具有更多的开销,但从运行时间来看,它在那里并没有开销。

    两种实现最终都会对 collection 内容进行一次迭代,并且迭代期间打印出元素。

    【讨论】:

    • 您提到的对象创建开销,您是指正在创建的Stream 还是单个对象? AFAIK,Stream 不会复制元素。
    • 这个答案似乎与甲骨文公司开发Java核心库的绅士写的优秀答案相矛盾。
    【解决方案2】:

    对于如图所示的简单情况,它们大多相同。但是,存在许多可能很重要的细微差别。

    一个问题是订购。对于Stream.forEach,顺序是undefined。顺序流不太可能发生这种情况,但它在 Stream.forEach 的规范内以任意顺序执行。这在并行流中确实经常发生。相比之下,Iterable.forEach 总是按照Iterable 的迭代顺序执行,如果指定了一个。

    另一个问题是副作用。 Stream.forEach 中指定的操作必须是不干扰。 (参见java.util.stream package doc。)Iterable.forEach 可能有更少的限制。对于java.util 中的集合,Iterable.forEach 通常会使用该集合的Iterator,其中大部分设计为fail-fast,如果在迭代期间对集合进行结构修改,则会抛出ConcurrentModificationException。但是,在迭代期间允许进行非结构性修改。例如,ArrayList class documentation 表示“仅设置元素的值不是结构修改”。因此,ArrayList.forEach 的操作可以毫无问题地在基础ArrayList 中设置值。

    并发集合再次不同。它们不是快速失败,而是设计为weakly consistent。完整的定义在那个链接上。不过,简而言之,请考虑ConcurrentLinkedDeque。传递给其forEach 方法的操作 允许修改底层双端队列,甚至在结构上,并且永远不会抛出ConcurrentModificationException。但是,发生的修改可能在此迭代中可见,也可能不可见。 (因此“弱”一致性。)

    如果Iterable.forEach 正在迭代一个同步集合,那么还有另一个区别是可见的。在这样的集合上,Iterable.forEachtakes the collection's lock 一次,并在对操作方法的所有调用中保持它。 Stream.forEach 调用使用集合的分离器,它不锁定,并且依赖于不干涉的普遍规则。支持流的集合可以在迭代期间被修改,如果是,则可能导致ConcurrentModificationException 或不一致的行为。

    【讨论】:

    • Iterable.forEach takes the collection's lock。这些信息来自哪里?我在 JDK 源代码中找不到这种行为。
    • @Stuart,您能否详细说明不干扰。 Stream.forEach() 也会抛出 ConcurrentModificationException(至少对我来说)。
    • @yuranos87 许多集合,例如ArrayList,对并发修改有相当严格的检查,因此经常会抛出ConcurrentModificationException。但这并不能保证,特别是对于并行流。您可能会得到意想不到的答案,而不是 CME。还要考虑对流源的非结构修改。对于并行流,您不知道哪个线程将处理特定元素,也不知道它在修改时是否已被处理。这会设置一个竞争条件,您可能会在每次运行中获得不同的结果,并且永远不会获得 CME。
    【解决方案3】:

    这个答案与循环的各种实现的性能有关。它仅与非常经常调用的循环(如数百万次调用)略微相关。在大多数情况下,循环的内容将是迄今为止最昂贵的元素。对于您经常循环的情况,这可能仍然很有趣。

    您应该在目标系统下重复此测试,因为这是特定于实现的 (full source code)。

    我在一台快速的 Linux 机器上运行 openjdk 版本 1.8.0_111。

    我编写了一个测试,使用此代码在 List 上循环 10^6 次,integers(10^0 -> 10^5 个条目)大小不一。

    结果如下,最快的方法取决于列表中的条目数量。

    但在最糟糕的情况下,循环 10^5 个条目 10^6 次对于表现最差的人来说需要 100 秒,因此其他考虑因素在几乎所有情况下都更为重要。

    public int outside = 0;
    
    private void iteratorForEach(List<Integer> integers) {
        integers.forEach((ii) -> {
            outside = ii*ii;
        });
    }
    
    private void forEach(List<Integer> integers) {
        for(Integer next : integers) {
            outside = next * next;
        }
    }
    
    private void forCounter(List<Integer> integers) {
        for(int ii = 0; ii < integers.size(); ii++) {
            Integer next = integers.get(ii);
            outside = next*next;
        }
    }
    
    private void iteratorStream(List<Integer> integers) {
        integers.stream().forEach((ii) -> {
            outside = ii*ii;
        });
    }
    

    这是我的计时:毫秒/函数/列表中的条目数。 每次运行是 10^6 循环。

                               1    10    100    1000    10000
           iterator.forEach   27   116    959    8832    88958
                   for:each   53   171   1262   11164   111005
             for with index   39   112    920    8577    89212
    iterable.stream.forEach  255   324   1030    8519    88419
    

    如果你重复实验,我发布了full source code。请编辑此答案并使用测试系统的符号添加您的结果。


    使用 MacBook Pro、2.5 GHz Intel Core i7、16 GB、macOS 10.12.6:

                               1    10    100    1000    10000
           iterator.forEach   27   106   1047    8516    88044
                   for:each   46   143   1182   10548   101925
             for with index   49   145    887    7614    81130
    iterable.stream.forEach  393   397   1108    8908    88361
    

    Java 8 Hotspot VM - 3.4GHz Intel Xeon,8 GB,Windows 10 Pro

                                1    10    100    1000    10000
            iterator.forEach   30   115    928    8384    85911
                    for:each   40   125   1166   10804   108006
              for with index   30   120    956    8247    81116
     iterable.stream.forEach  260   237   1020    8401    84883
    

    Java 11 Hotspot VM - 3.4GHz Intel Xeon,8 GB,Windows 10 Pro
    (与上面相同的机器,不同的JDK版本)

                                1    10    100    1000    10000
            iterator.forEach   20   104    940    8350    88918
                    for:each   50   140    991    8497    89873
              for with index   37   140    945    8646    90402
     iterable.stream.forEach  200   270   1054    8558    87449
    

    Java 11 OpenJ9 虚拟机 - 3.4GHz Intel Xeon,8 GB,Windows 10 Pro
    (与上面相同的机器和JDK版本,不同的VM)

                                1    10    100    1000    10000
            iterator.forEach  211   475   3499   33631   336108
                    for:each  200   375   2793   27249   272590
              for with index  384   467   2718   26036   261408
     iterable.stream.forEach  515   714   3096   26320   262786
    

    Java 8 热点虚拟机 - 2.8GHz AMD、64 GB、Windows Server 2016

                                1    10    100    1000    10000
            iterator.forEach   95   192   2076   19269   198519
                    for:each  157   224   2492   25466   248494
              for with index  140   368   2084   22294   207092
     iterable.stream.forEach  946   687   2206   21697   238457
    

    Java 11 热点虚拟机 - 2.8GHz AMD、64 GB、Windows Server 2016
    (与上面相同的机器,不同的JDK版本)

                                1    10    100    1000    10000
            iterator.forEach   72   269   1972   23157   229445
                    for:each  192   376   2114   24389   233544
              for with index  165   424   2123   20853   220356
     iterable.stream.forEach  921   660   2194   23840   204817
    

    Java 11 OpenJ9 虚拟机 - 2.8GHz AMD,64 GB,Windows Server 2016
    (与上面相同的机器和JDK版本,不同的VM)

                                1    10    100    1000    10000
            iterator.forEach  592   914   7232   59062   529497
                    for:each  477  1576  14706  129724  1190001
              for with index  893   838   7265   74045   842927
     iterable.stream.forEach 1359  1782  11869  104427   958584
    

    您选择的 VM 实现也会对 Hotspot/OpenJ9/等产生影响。

    【讨论】:

    • 这是一个非常好的答案,谢谢!但从第一眼(也从第二眼)看,并不清楚哪种方法对应于哪种实验。
    • 我觉得这个答案需要更多的代码测试支持:)。
    • 测试示例 +1
    • iterator.forEach 应更改为 iterable.forEach。迭代器没有 foreach()。
    【解决方案4】:

    Collection.forEach() 使用集合的迭代器(如果指定了一个)。这意味着定义了项目的处理顺序。相比之下,Collection.stream().forEach() 的处理顺序是不确定的。

    在大多数情况下,我们选择两者中的哪一个并不重要。 并行流允许我们在多个线程中执行流,在这种情况下,执行顺序是不确定的。 Java 只需要在调用任何终端操作(例如 Collectors.toList())之前完成所有线程。 让我们看一个示例,我们首先在集合上直接调用 forEach(),然后在并行流上调用:

    list.forEach(System.out::print);
    System.out.print(" ");
    list.parallelStream().forEach(System.out::print);
    

    如果我们多次运行代码,我们会看到 list.forEach() 按插入顺序处理项目,而 list.parallelStream().forEach() 每次运行都会产生不同的结果。 一种可能的输出是:

    ABCD CDBA
    

    另一个是:

    ABCD DBCA
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-11
    • 2015-05-19
    • 2016-04-04
    • 2016-06-11
    • 2019-10-25
    相关资源
    最近更新 更多