Vla*_*adS 271 java collections java-8 java-stream
据我所知.stream()
,我可以使用链操作.filter()
或使用并行流.但是如果我需要执行小操作(例如,打印列表的元素),它们之间的区别是什么?
collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)
Stu*_*rks 268
对于诸如图示的简单情况,它们大多是相同的.但是,有许多微妙的差异可能很重要.
一个问题是订购.有Stream.forEach
,订单未定义.顺序流不太可能发生,但仍然在规范内Stream.forEach
以某种任意顺序执行.这在并行流中经常发生.相反,如果指定了一个,Iterable.forEach
则始终以迭代顺序执行Iterable
.
另一个问题是副作用.指定的操作Stream.forEach
必须是非干扰的.(请参阅java.util.stream包文档.)Iterable.forEach
可能具有较少的限制.对于集合中java.util
,Iterable.forEach
通常会使用该集合Iterator
,其中大部分都设计为快速失败,ConcurrentModificationException
如果在迭代期间对集合进行结构修改,则会抛出该集合.但是,修改是不是结构性的迭代过程中允许的.例如,ArrayList类文档说"仅设置元素的值不是结构修改".因此,ArrayList.forEach
允许操作for 在底层设置值ArrayList
而没有问题.
并发集合再次不同.它们不是快速失败,而是设计为弱一致的.完整的定义是在那个链接上.简而言之,请考虑一下ConcurrentLinkedDeque
.传递给它的操作forEach
方法是允许修改底层双端队列,即使结构上,并且ConcurrentModificationException
永远不会抛出.但是,在此迭代中发生的修改可能会也可能不会显示.(因此"弱"的一致性.)
如果Iterable.forEach
在同步集合上进行迭代,则还可以看到另一个差异.在这样的集合上,Iterable.forEach
获取集合的锁定一次并将其保存在对action方法的所有调用中.该Stream.forEach
呼叫使用集合的分裂器,它不锁定,并且依赖于主流的无干扰规则.在迭代期间可以修改支持流的集合,如果是,则ConcurrentModificationException
可能导致或不一致的行为.
Ang*_*chs 25
这个答案关注于循环的各种实现的性能.它与称为VERY OFTEN的循环(如数百万次调用)仅略微相关.在大多数情况下,循环的内容将是迄今为止最昂贵的元素.对于经常循环的情况,这可能仍然很有意义.
您应该在目标系统下重复此测试,因为这是特定于实现的(完整源代码).
我在一台快速的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;
});
}
Run Code Online (Sandbox Code Playgroud)
这是我的时间:毫秒/功能/列表中的条目数.每次运行是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
Run Code Online (Sandbox Code Playgroud)
如果您重复实验,我会发布完整的源代码.请编辑此答案,并使用已测试系统的表示法为您添加结果.
1 10 100 1000 10000
for with index 49 145 887 7614 81130
iterator.forEach 27 106 1047 8516 88044
for:each 46 143 1182 10548 101925
iterable.stream.forEach 393 397 1108 8908 88361
Run Code Online (Sandbox Code Playgroud)
(MacBook Pro,2.5 GHz Intel Core i7,16 GB,macOS 10.12.6)
你提到的两个没有区别,至少在概念上,这Collection.forEach()
只是一个简写.
在内部,stream()
由于对象创建,版本有更多的开销,但是查看运行时间,它既没有开销.
两种实现最终都会迭代collection
内容一次,并且在迭代期间打印出元素.
Collection.forEach() 使用集合的迭代器(如果指定了)。这意味着项目的处理顺序已定义。相反,Collection.stream().forEach() 的处理顺序是未定义的。
在大多数情况下,我们选择两者中的哪一个并没有什么区别。并行流允许我们在多个线程中执行流,在这种情况下,执行顺序是不确定的。Java 仅要求所有线程在调用任何终端操作(例如 Collectors.toList())之前完成。让我们看一个示例,其中我们首先直接在集合上调用 forEach(),然后在并行流上调用:
list.forEach(System.out::print);
System.out.print(" ");
list.parallelStream().forEach(System.out::print);
Run Code Online (Sandbox Code Playgroud)
如果我们多次运行代码,我们会看到 list.forEach() 按插入顺序处理项目,而 list.parallelStream().forEach() 在每次运行时都会产生不同的结果。一种可能的输出是:
ABCD CDBA
Run Code Online (Sandbox Code Playgroud)
另一种是:
ABCD DBCA
Run Code Online (Sandbox Code Playgroud)