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

Posted

技术标签:

【中文标题】如何确保 java8 流中的处理顺序?【英文标题】:How to ensure order of processing in java8 streams? 【发布时间】:2015-05-26 19:36:47 【问题描述】:

我想处理 XML java 对象中的列表。我必须确保按照收到的顺序处理所有元素。

因此,我是否应该在我使用的每个 stream 上调用 sequentiallist.stream().sequential().filter().forEach()

或者只要我不使用并行性,只使用流就足够了吗? list.stream().filter().forEach()

【问题讨论】:

没有。您需要使用 forEachOrderedlist.stream().filter().forEachOrdered() 【参考方案1】:

简而言之:

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

更多细节:

顺序 vs 并行 vs 无序:

Javadocs

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.

流排序:

Javadocs

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

如果流是有序的,大多数操作都被限制在 遇到顺序中的元素;如果流的源是 列表包含 [1, 2, 3],然后是执行 map(x -> x*2) 的结果 必须是 [2, 4, 6]。但是,如果源没有定义的遭遇 顺序,那么值 [2, 4, 6] 的任何排列都是有效的 结果。

对于顺序流,是否存在相遇顺序 不影响性能,只影响确定性。如果流是有序的, 在相同的流水线上重复执行相同的流管道 source 将产生相同的结果;如果没有订购, 重复执行可能会产生不同的结果。

对于并行流,有时可以放宽排序约束 实现更高效的执行。某些聚合操作,例如 过滤重复项(distinct())或分组减少 (Collectors.groupingBy()) 可以更有效地实现,如果 元素的顺序不相关。同样,操作是 本质上与遇到顺序相关,例如 limit(),可能需要 缓冲以确保正确排序,破坏了 并行性。在流有遇到顺序的情况下,但是 用户并不特别关心遇到的顺序,明确地 使用 unordered() 对流进行降序可能会提高并行性 一些有状态或终端操作的性能。然而,大多数 流管道,例如上面的“块权重总和”示例, 即使在排序约束下仍然有效地并行化。

【讨论】:

【参考方案2】:

你问错问题了。您询问的是sequentialparallel,而您想按顺序 处理项目,因此您必须询问ordering。如果你有一个 ordered 流并执行保证保持顺序的操作,那么流是并行处理还是顺序处理都没有关系;实现将保持顺序。

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

另请参阅java.util.stream package documentation 的“Ordering” section。

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

这可能非常微妙,例如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());

整个操作可能会从并行执行中受益,但无论您使用并行流还是顺序流,生成的列表都会始终按正确的顺序排列。

【讨论】:

是的,很好的答案。我发现的一件事是,我们使用的术语,至少在英语中,例如“之前”、“之后”等等,是非常模棱两可的。这里有两种排序方式:1)遇到顺序(也称空间顺序),2)处理顺序(也称时间顺序)。考虑到这一区别,在讨论遇到顺序时使用“left of”或“right of”等词,在讨论处理顺序时使用“earlier than”或“later than”等词可能会有所帮助。 @JoshC。这取决于实际的集合类型。 Sets 通常不会,除非是 SortedSetLinkedHashSetMapkeySet()entrySet()values())的集合视图继承 Map 的策略,即当映射为 SortedMapLinkedHashMap 时排序。该行为由the collection’s spliterator 报告的特征决定。 Collectiondefault 实现不报告 ORDERED 特性,因此它是无序的,除非被覆盖。 值得注意的是,forEachOrdered 仅在使用并行流时与 forEach 不同 - 但在订购事项时无论如何都要使用它,以防蒸汽方法发生变化...... @Rafael 你指的是那个页面上的哪个例子? @Rafael 输出前面是“它打印输出 类似 到以下内容:”,这意味着“3 4 1 6 2 5 7 8”对于任何可能的输出与列表的顺序不匹配(应该是 8 7 6 5 4 3 2 1)。

以上是关于如何确保 java8 流中的处理顺序?的主要内容,如果未能解决你的问题,请参考以下文章

确保线程池中的任务执行顺序

如何使用 Java 8 lambda 从流中获取一系列项目?

如何确保两个流中的两个内核同时发送到 GPU 运行?

Java8实战使用并行流

java8 stream流操作

如何确保JavaScript的执行顺序