我知道对于 .stream()
,我可以使用像 .filter()
这样的链式操作或使用并行流。但是如果我需要执行一些小操作(例如,打印列表的元素),它们之间有什么区别呢?
collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);
原文由 VladS 发布,翻译遵循 CC BY-SA 4.0 许可协议
我知道对于 .stream()
,我可以使用像 .filter()
这样的链式操作或使用并行流。但是如果我需要执行一些小操作(例如,打印列表的元素),它们之间有什么区别呢?
collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);
原文由 VladS 发布,翻译遵循 CC BY-SA 4.0 许可协议
这个答案本身与循环的各种实现的性能有关。它只与被称为非常频繁(如数百万次调用)的循环略有相关。在大多数情况下,循环的内容将是迄今为止最昂贵的元素。对于经常循环的情况,这可能仍然很有趣。
您应该在目标系统下重复此测试,因为这是特定于实现的( 完整源代码)。
我在一台快速的 Linux 机器上运行 openjdk 版本 1.8.0_111。
我编写了一个测试,该测试使用此代码对 integers
(10^0 -> 10^5 个条目)的不同大小在列表上循环 10^6 次。
结果如下,最快的方法取决于列表中的条目数量。
但仍然在最坏的情况下,循环 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 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 热点虚拟机 - 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 热点虚拟机 - 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.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,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 热点虚拟机 - 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 虚拟机 - 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/等产生影响。
原文由 Angelo Fuchs 发布,翻译遵循 CC BY-SA 4.0 许可协议
15 回答8.4k 阅读
8 回答6.2k 阅读
1 回答4k 阅读✓ 已解决
3 回答2.2k 阅读✓ 已解决
2 回答3.1k 阅读
2 回答3.8k 阅读
3 回答1.7k 阅读✓ 已解决
对于如图所示的简单情况,它们大部分是相同的。但是,有许多细微的差异可能很重要。
一个问题是订购。对于
Stream.forEach
,顺序 未定义。它不太可能发生在顺序流中,但它仍然在Stream.forEach
的规范内,以某种任意顺序执行。这确实经常发生在并行流中。相比之下,Iterable.forEach
总是按照Iterable
的迭代顺序执行,如果指定的话。另一个问题是副作用。
Stream.forEach
中指定的操作必须是 无干扰的。 (请参阅 java.util.stream 包文档。)Iterable.forEach
可能具有更少的限制。 For the collections injava.util
,Iterable.forEach
will generally use that collection’sIterator
, most of which are designed to be fail-fast and which will throwConcurrentModificationException
如果集合在迭代过程中进行了结构修改。但是,在迭代期间允许 进行 非结构性修改。例如, ArrayList 类文档 说“仅仅设置元素的值不是结构修改”。因此,允许ArrayList.forEach
的操作在底层ArrayList
中设置值而没有问题。并发集合又是不同的。它们不是快速失败,而是设计为 弱一致。完整的定义在那个链接。不过,简而言之,请考虑
ConcurrentLinkedDeque
。传递给其forEach
方法 的操作 允许修改底层双端队列,甚至在结构上,并且ConcurrentModificationException
永远不会被抛出。但是,发生的修改可能在此迭代中可见,也可能不可见。 (因此具有“弱”一致性。)如果
Iterable.forEach
正在迭代同步集合,则还有一个区别是可见的。在这样的集合上,Iterable.forEach
获取一次集合的锁,并在对操作方法的所有调用中保持它。Stream.forEach
调用使用集合的拆分器,它不锁定,并且依赖于非干扰的普遍规则。可以在迭代期间修改支持流的集合,如果是,则可能会导致ConcurrentModificationException
或不一致的行为。