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

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

当前回答

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

您应该在目标系统下重复此测试,因为这是特定于实现的(完整源代码)。

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

我写了一个测试,在一个List上循环10^6次,对于整数(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

如果你重复这个实验,我发布了完整的源代码。请编辑这个答案,并添加你的结果与测试系统的符号。


使用MacBook Pro, 2.5 GHz英特尔酷睿i7, 16gb, 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, 8gb, 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, 8gb, 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 VM - 3.4GHz Intel Xeon, 8gb, Windows 10 Pro (与上述机器和JDK版本相同,不同虚拟机)

                            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 Hotspot VM - 2.8GHz AMD, 64gb, 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 Hotspot VM - 2.8GHz AMD, 64gb, 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 VM - 2.8GHz AMD, 64gb, Windows Server 2016 (与上述机器和JDK版本相同,不同虚拟机)

                            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

您所选择的虚拟机实现也会产生不同的效果,例如Hotspot/OpenJ9等。

其他回答

这里有很多好答案。

只是添加了Stuart(@user:1441122) Yuranos (@user:4470135)的评论,并在这里发布了一个答案。

@Yuranos,是的,我们在删除元素时得到ConcurrentModificationException,同时迭代两者。Stream.forEach()和forEach()之间的细微区别是:

Java显式地允许使用迭代器修改元素。相反,流应该是无干扰的。

list = [1,2,3,4] 让我们定义一个操作,删除列表的最后一个元素(4):

Consumer<Integer> removeElement = s -> {
    System.out.println(s + " " + list.size());
    if (s != null && s==1) {
        list.remove(4);
    }
};

收集forEach ()

list.forEach(removeElement);

由于forEach()是快速失败的,我们停止迭代并在下一个元素被处理之前看到一个异常:

1 4
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList.forEach(ArrayList.java:1252)
    at ReverseList.main(ReverseList.java:1)

流forEach ()

list.stream().forEach(removeElement);

1 4
2 3
3 3
null 3

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1380)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at ReverseList.main(ReverseList.java:1)

collection . foreach()使用集合的迭代器(如果指定了一个)。这意味着定义了项目的处理顺序。相反,Collection.stream(). foreach()的处理顺序是未定义的。

在大多数情况下,我们选择哪一个都没有区别。 并行流允许我们在多个线程中执行流,在这种情况下,执行顺序是未定义的。Java只要求在调用任何终端操作(如collections . 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

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

您应该在目标系统下重复此测试,因为这是特定于实现的(完整源代码)。

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

我写了一个测试,在一个List上循环10^6次,对于整数(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

如果你重复这个实验,我发布了完整的源代码。请编辑这个答案,并添加你的结果与测试系统的符号。


使用MacBook Pro, 2.5 GHz英特尔酷睿i7, 16gb, 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, 8gb, 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, 8gb, 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 VM - 3.4GHz Intel Xeon, 8gb, Windows 10 Pro (与上述机器和JDK版本相同,不同虚拟机)

                            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 Hotspot VM - 2.8GHz AMD, 64gb, 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 Hotspot VM - 2.8GHz AMD, 64gb, 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 VM - 2.8GHz AMD, 64gb, Windows Server 2016 (与上述机器和JDK版本相同,不同虚拟机)

                            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

您所选择的虚拟机实现也会产生不同的效果,例如Hotspot/OpenJ9等。

对于简单的例子,如上图所示,它们大多是相同的。然而,有一些细微的差异可能是重要的。

其中一个问题是排序。与流。对于每一个,顺序是没有定义的。它不太可能发生在顺序流中,不过,它仍然在流的规范中。forEach以任意顺序执行。这在并行流中确实经常发生。相比之下,Iterable。如果指定了Iterable, forEach总是按照Iterable的迭代顺序执行。

Another issue is with side effects. The action specified in Stream.forEach is required to be non-interfering. (See the java.util.stream package doc.) Iterable.forEach potentially has fewer restrictions. For the collections in java.util, Iterable.forEach will generally use that collection's Iterator, most of which are designed to be fail-fast and which will throw ConcurrentModificationException if the collection is structurally modified during the iteration. However, modifications that aren't structural are allowed during iteration. For example, the ArrayList class documentation says "merely setting the value of an element is not a structural modification." Thus, the action for ArrayList.forEach is allowed to set values in the underlying ArrayList without problems.

并发集合又是不同的。它们被设计成弱一致性,而不是快速失败。完整的定义就在那个环节。不过,简单地考虑一下ConcurrentLinkedDeque。传递给它的forEach方法的动作被允许修改底层的deque,甚至是结构性的,并且ConcurrentModificationException永远不会被抛出。然而,所发生的修改在此迭代中可能可见,也可能不可见。(因此才有了“弱”一致性。)

Still another difference is visible if Iterable.forEach is iterating over a synchronized collection. On such a collection, Iterable.forEach takes the collection's lock once and holds it across all the calls to the action method. The Stream.forEach call uses the collection's spliterator, which does not lock, and which relies on the prevailing rule of non-interference. The collection backing the stream could be modified during iteration, and if it is, a ConcurrentModificationException or inconsistent behavior could result.

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

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

这两种实现最终都对集合内容进行一次迭代,并在迭代期间打印出元素。