如何确保java8流中的处理顺序?


148

我想处理XMLJava对象中的列表。我必须确保处理所有元素,以便收到它们。

因此sequentialstream我应该使用它吗? list.stream().sequential().filter().forEach()

还是只要我不使用并行性就足够使用流? list.stream().filter().forEach()

Answers:


338

您在问错问题。您正在询问sequentialvs. parallel而要按顺序处理项目,因此您必须询问订购。如果您有一个有序的流并执行保证维持顺序的操作,则该流是并行还是顺序处理都无关紧要;实施将维持秩序。

有序属性不同于并行与顺序。例如,如果你调用stream()一个HashSet同时调用流将是无序stream()List返回的有序流。请注意,您可以致电unordered()以解除订购合同并可能提高性能。一旦流没有排序,就无法重新建立排序。(将无序流转换为有序流的唯一方法是调用sorted,但是生成的顺序不一定是原始顺序)。

又见“订购”一节中的java.util.stream包文档

为了确保在整个流操作中维持顺序,您必须研究流源,所有中间操作和终端操作的文档,以了解它们是否维持顺序(或源是否在第一个顺序中具有顺序)地点)。

这可能非常微妙,例如,Stream.iterate(T,UnaryOperator)创建有序流而Stream.generate(Supplier)创建无序流。请注意,由于没有保持顺序,您在问题中也犯了一个常见错误。如果要以保证的顺序处理流的元素,则必须使用。forEach forEachOrdered

因此,如果您list的问题确实是java.util.List,则其stream()方法将返回有序流,并且filter不会更改顺序。因此,如果调用list.stream().filter() .forEachOrdered(),则将按顺序顺序处理所有元素,而对于list.parallelStream().filter().forEachOrdered()元素,则可能会并行处理(例如,通过过滤器),但仍将按顺序调用终端操作(显然,这将减少并行执行的好处) 。

例如,如果您使用类似

List<…> result=inputList.parallelStream().map(…).filter(…).collect(Collectors.toList());

整个操作可能会受益于并行执行,但是无论您使用并行流还是顺序流,结果列表始终会以正确的顺序排列。


48
是的,很好的答案。我发现的一件事是,我们使用的术语(至少用英语表示)是非常含糊的,至少在英语中。这里有两种排序:1)遇到顺序(也称为空间顺序),和2)处理顺序(也称为时间顺序)。考虑到这种区别,在讨论遇到顺序时使用诸如“左边”或“右边”之类的词,而在讨论处理顺序时使用“早于”或“晚于”的词可能会有所帮助。
斯图尔特(Stuart)标志着

我了解List<>会保留订单,但会Collection<>吗?
Josh C.

5
@JoshC。这取决于实际的收集类型。Set除非是SortedSet或,否则通常不会LinkedHashSetMapkeySet()entrySet()values())的集合视图继承Map的策略,即,当映射为SortedMap或时进行排序LinkedHashMap。行为由集合的拆分器报告的特征确定。的default实现Collection不会报告该ORDERED特征,因此它是无序的,除非被覆盖。
Holger

@Holger我有一个问题,可能与您回答的一小部分有关。
纳曼

1
值得一提的是,这forEachOrdered仅与forEach使用并行流不同-但在良好的实践中,如果订购方法发生变化,无论如何在订购时都应使用它...
Steve Chambers

0

简而言之:

排序取决于源数据结构和中间流操作。假设您正在使用,List则应该对处理进行排序(因为filter此处不会更改顺序)。

更多细节:

顺序vs并行vs无序:

Java文档

S sequential()
Returns an equivalent stream that is sequential. May return itself, either because the stream was already sequential, or because the underlying stream state was modified to be sequential.
This is an intermediate operation.
S parallel()
Returns an equivalent stream that is parallel. May return itself, either because the stream was already parallel, or because the underlying stream state was modified to be parallel.
This is an intermediate operation.
S unordered()
Returns an equivalent stream that is unordered. May return itself, either because the stream was already unordered, or because the underlying stream state was modified to be unordered.
This is an intermediate operation.

流排序:

Java文档

流可能具有也可能没有定义的遇到顺序。流是否具有遇到顺序取决于源和中间操作。某些流源(例如List或数组)在本质上是有序的,而其他流源(例如HashSet)则不是。某些中间操作(例如sorted())可能会在其他情况下将无顺序流强加一个遇到顺序,而其他一些操作可能会使有序流(例如BaseStream.unordered())变得无序。此外,某些终端操作可能会忽略遇到顺序,例如forEach()。

如果对流进行了排序,则大多数操作将被约束为按其遇到顺序对元素进行操作;如果流的源是包含[1、2、3]的列表,则执行map(x-> x * 2)的结果必须为[2、4、6]。但是,如果源没有定义的遇到顺序,则值[2、4、6]的任何排列都是有效的结果。

对于顺序流,是否存在相遇顺序不会影响性能,只会影响确定性。如果订购了一个流,则在相同的源上重复执行相同的流管道将产生相同的结果;如果未订购,重复执行可能会产生不同的结果。

对于并行流,放宽排序约束有时可以使执行效率更高。如果元素的顺序无关紧要,则可以更有效地实现某些聚合操作,例如过滤重复项(distinct())或分组的约简(Collectors.groupingBy())。同样,本质上与遇到顺序相关的操作(例如limit())可能需要缓冲以确保正确的顺序,从而削弱了并行性的优势。如果流具有遇到顺序,但用户并不特别关心该遇到顺序,则使用unordered()对流进行明确排序可以提高某些状态操作或终端操作的并行性能。但是,大多数流管道,例如上面的“块权重之和”示例,

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.