Answers:
对于诸如所示的简单情况,它们基本上是相同的。但是,有许多细微的差别可能很重要。
一个问题是订购。使用Stream.forEach
,顺序不确定。顺序流不太可能发生,但是,它在规范中可以Stream.forEach
以任意顺序执行。这确实在并行流中经常发生。相反,如果指定Iterable.forEach
了Iterable
,则总是按的迭代顺序执行。
另一个问题是副作用。Stream.forEach
必须指定中的动作为非干扰动作。(请参阅java.util.stream软件包doc。)Iterable.forEach
可能会有更少的限制。对于中的集合java.util
,Iterable.forEach
通常将使用该集合的Iterator
,其中大多数被设计为快速失败的,并且ConcurrentModificationException
如果在迭代过程中对集合进行结构修改,则将抛出该集合。但是,在迭代过程中允许进行非结构化的修改。例如,ArrayList类文档说“仅设置元素的值不是结构修改”。因此,针对ArrayList.forEach
允许在底层中设置值ArrayList
而不会出现问题。
并发集合又一次不同。它们不是快速失败,而是设计为弱一致性。完整定义在该链接上。不过,请简要考虑一下ConcurrentLinkedDeque
。传递给其forEach
方法的操作被允许修改底层双端队列,甚至在结构上也可以修改,并且ConcurrentModificationException
永远不会被抛出。但是,发生的修改在此迭代中可能可见,也可能不可见。(因此保持“弱”一致性。)
如果Iterable.forEach
在同步的集合上进行迭代,则可以看到另一个差异。在这样的集合上,Iterable.forEach
获取一次该集合的锁,并在对action方法的所有调用中保持该锁。该Stream.forEach
调用使用集合的分隔符,该分隔符不会锁定,并且依赖于流行的不干扰规则。支持该流的集合可以在迭代期间进行修改,如果是,则ConcurrentModificationException
可能导致行为不一致或行为不一致。
ArrayList
都对并发修改进行了相当严格的检查,因此经常会抛出ConcurrentModificationException
。但这并不能保证,特别是对于并行流。您可能会得到意想不到的答案,而不是CME。还应考虑对流源进行非结构性修改。对于并行流,您不知道哪个线程将处理特定元素,也不知道在修改该元素时是否已对其进行处理。这将设置一个竞争条件,在此条件下,您每次运行可能会得到不同的结果,而永远不会获得CME。
该答案与循环的各种实现的性能有关。它与被称为“非常频繁”的循环(如数百万个调用)的边际相关。在大多数情况下,循环的内容将是迄今为止最昂贵的元素。对于确实经常循环的情况,这可能仍然很有趣。
您应该在目标系统下重复此测试,因为这是特定于实现的(完整的源代码)。
我在快速的Linux机器上运行openjdk版本1.8.0_111。
我编写了一个测试,使用此代码在列表上循环10 ^ 6次,该代码的大小各不相同integers
(10 ^ 0-> 10 ^ 5个条目)。
结果如下,最快的方法取决于列表中条目的数量。
但是,即使在最坏的情况下,表现最差的人也要花10秒循环10 ^ 5个条目10 ^ 6次,因此实际上在所有情况下其他考虑因素都更为重要。
public int outside = 0;
private void forCounter(List<Integer> integers) {
for(int ii = 0; ii < integers.size(); ii++) {
Integer next = integers.get(ii);
outside = next*next;
}
}
private void forEach(List<Integer> integers) {
for(Integer next : integers) {
outside = next * next;
}
}
private void iteratorForEach(List<Integer> integers) {
integers.forEach((ii) -> {
outside = ii*ii;
});
}
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 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 VM- 3.4 GHz 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 Hotspot VM-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 Hotspot VM-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 VM-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 / etc。
您提到的两者之间没有任何区别,至少从概念上讲,这Collection.forEach()
只是一个简写。
在内部,stream()
由于创建对象,该版本的开销有所增加,但是从运行时间来看,该版本都没有开销。
两种实现都最终对collection
内容进行一次迭代,并在迭代过程中打印出元素。
Stream
创建对象还是单个对象?AFAIK,a Stream
不重复元素。
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
Iterable.forEach takes the collection's lock
。这些信息来自哪里?我在JDK源代码中找不到这种行为。