我理解使用.stream()
,我可以使用.filter()
等链操作或使用并行流。但是,如果我需要执行小操作(例如,打印列表的元素),它们之间的区别是什么?
collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);
答案 0 :(得分:242)
对于如图所示的简单情况,它们大多是相同的。但是,有许多微妙的差异可能很重要。
一个问题是订购。对于Stream.forEach
,订单为未定义。它不太可能出现在顺序流中,它仍然在Stream.forEach
的规范中以某种任意顺序执行。这在并行流中经常发生。相比之下,Iterable.forEach
总是以Iterable
的迭代顺序执行,如果指定了一个。{/ p>
另一个问题是副作用。 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.forEach
takes the collection's lock一次并将其保存在对action方法的所有调用中。 Stream.forEach
调用使用了集合的分裂器,它没有锁定,并且依赖于主流的非干扰规则。在迭代期间可以修改支持流的集合,如果是,则可能导致ConcurrentModificationException
或不一致的行为。
答案 1 :(得分:15)
这个答案关注的是循环的各种实现的性能。它与称为VERY OFTEN的循环(如数百万次调用)仅略微相关。在大多数情况下,循环的内容将是迄今为止最昂贵的元素。对于经常循环的情况,这可能仍然很有用。
您应该在目标系统下重复此测试,因为这是特定于实现的(full source code)。
我在快速的Linux机器上运行openjdk版本1.8.0_111。
我编写了一个测试,使用此代码在integers
(10 ^ 0 - > 10 ^ 5个条目)的不同大小上,在List上循环10 ^ 6次。
结果如下,最快的方法因列表中的条目数量而异。
但是在最糟糕的情况下,对于表现最差的人来说,循环超过10 ^ 5个条目10 ^ 6次需要100秒,所以其他考虑在几乎所有情况下都更为重要。
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
for with index 39 112 920 8577 89212
iterator.forEach 27 116 959 8832 88958
for:each 53 171 1262 11164 111005
iterable.stream.forEach 255 324 1030 8519 88419
如果您重复实验,我发布了full source code。请编辑此答案,并使用已测试系统的表示法为您添加结果。
I got:
1 10 100 1000 10000
iterator.forEach 27 106 1047 8516 88044
for:each 46 143 1182 10548 101925
iterable.stream.forEach 393 397 1108 8908 88361
for with index 49 145 887 7614 81130
(MacBook Pro,2.5 GHz Intel Core i7,16 GB,macOS 10.12.6)
答案 2 :(得分:8)
你提到的两个没有区别,至少在概念上,Collection.forEach()
只是一个简写。
在内部stream()
版本由于对象创建而有更多的开销,但是查看运行时间它没有开销。
两种实现最终都会迭代collection
内容一次,在迭代期间打印出元素。
答案 3 :(得分:1)
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