在Java 8中,顺序流(sequential)和有序流(ordered)是否保证按照遇到的顺序执行操作?

12
有没有保证在处理顺序流(sequential and ordered Stream)时,操作是按照遇到的顺序(encounter order)进行的?
我的意思是,如果我有这样的代码:
IntStream.range(0, 5)
        .map(i -> {
            myFunction(i);
            return i * 2;
         })
         .boxed()
         .collect(toList());

有没有保证它会按照生成范围的遇到顺序执行myFunction()调用?

我找到了Stream类的草稿JavaDocs,其中明确说明:

对于顺序流水线,如果流水线源具有定义的遇到顺序,则所有操作都按照流水线源的遇到顺序执行。

但在官方JavaDocs中,这行被删除了。现在仅讨论选定方法的遇到顺序。Java.util.stream文档包中的Side-effects段落说明:

即使管道被限制为产生与流源的遇到顺序一致的结果(例如,IntStream.range(0,5).parallel().map(x -> x*2).toArray()必须产生[0, 2, 4, 6, 8]),也不能保证映射函数应用于单个元素的顺序或任何行为参数在给定元素上执行的线程。

但它没有提及顺序流,而且示例是针对并行流的(我的理解是它对顺序和并行流都适用,但这一点我不确定)。

另一方面,在Ordering部分中还指出:
如果一个流是有序的,大多数操作会按照元素的顺序进行操作;如果流的源是包含 [1, 2, 3] 的 List,则执行 map(x -> x*2) 的结果必须为 [2, 4, 6]。但是,如果源没有定义的遇到顺序,则任何值的排列 [2, 4, 6] 都是有效结果。但是,这次它从“对元素进行操作”开始,但示例是关于生成的流的,因此我不确定它们是否考虑了副作用,而副作用正是这个问题所关注的内容。

2
这个回答解决了你的问题吗?https://dev59.com/zVoV5IYBdhLWcg3wkfW9 - Tunaki
我已经扩展了问题,包括排序部分,这听起来对我来说好像与副作用部分相矛盾。 - piotrek
也许这个可以帮到你:https://dev59.com/gF4b5IYBdhLWcg3wYQqZ - Jean-Baptiste Yunès
1个回答

2
我认为我们可以从这个明确的句子被删除的事实中学到很多东西。这个问题似乎与“Stream.forEach是否尊重顺序流的相遇顺序?”的问题密切相关。Brian Goetz的答案基本上说,尽管当前实现的Stream在顺序流上调用forEach时不会忽略顺序,但是根据规范,forEach即使对于顺序流,也有忽略相遇顺序的自由。
现在考虑{{link3:Stream类文档的以下部分}}:
进行计算时,流操作被组合成一个“流水线”。流水线由一个源(可以是数组、集合、生成器函数、I/O通道等)、零个或多个“中间操作”(将一个流转换为另一个流,例如filter(Predicate))和一个终端操作(产生结果或副作用,例如count()或forEach(Consumer))组成。流是惰性的;只有在启动终端操作时才会对源数据进行计算,并且仅在需要时才消耗源元素。
由于是终端操作决定元素是否需要以及它们是否按照遇到的顺序需要,终端操作忽略遇到的顺序的自由也意味着以任意顺序消耗、处理元素。
请注意,不仅仅是 forEach 可以做到这一点。一个 Collector 具有报告 UNORDERED 特征的能力,例如 Collectors.toSet() 不依赖于遇到的顺序。显然,像 count() 这样的操作也不依赖于顺序,在 Java 9 中甚至可能在没有任何元素处理的情况下返回。再举一个例子,可以考虑 IntStream#sum()
过去,实现在向上传播无序特征时过于急切,参见 “Is this a bug in Files.lines(), or am I misunderstanding something about parallel streams?”,其中终端操作影响了 skip 步骤的结果,这就是为什么当前实现对此类优化持谨慎态度的原因,以避免类似错误,但这并不排除出现这种优化的可能性,只是需要更加小心地实现...
因此,目前很难想象如何在顺序流中利用无序评估的自由来获得性能优势,但正如在与forEach相关的问题中所述,这并不意味着有任何保证。

1
@ Hulk:无论元素的顺序如何,对int求和是否会溢出并不取决于它们的顺序。 a+b+c的结果与c+b+ab+c+a等所有int值的结果相同,即使发生溢出也是如此。这就是为什么我选择了IntStream#sum()而不是DoubleStream#sum(),在后者中顺序确实很重要(尽管DoubleStream#sum()的文档明确指出加法的顺序将未定义)。 - Holger
我思考了一会儿后得出了同样的结论 - 这就是为什么我已经删除了我的评论。对于造成的任何困惑,我很抱歉 ;) - Hulk
目前很难想象如何在顺序流中利用无序评估的自由来获得性能优势。但是,通过使用.sorted().findAny()可以消除排序。如果无序性可以传播回源分裂器(实际上不行),则某些数据结构遍历也可以进行优化。 - the8472
@the8472 好的,消除排序是唯一的优化措施,已经被移除了。但我从未说过没有其他可能性。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接