灵感来自 this question ,我开始玩有序流与无序流、并行流与顺序流以及尊重遇到顺序的终端操作与不尊重它的终端操作。
在链接问题的一个答案中,显示了与此类似的代码:
List<Integer> ordered = Arrays.asList(
1, 2, 3, 4, 4, 3, 2, 1, 1, 2, 3, 4, 4, 3, 2, 1, 1, 2, 3, 4);
List<Integer> result = new CopyOnWriteArrayList<>();
ordered.parallelStream().forEach(result::add);
System.out.println(ordered);
System.out.println(result);
而且列表确实不同。 无序
列表甚至会从一次运行到另一次运行发生变化,表明结果实际上是不确定的。
所以我创建了另一个示例:
CopyOnWriteArrayList<Integer> result2 = ordered.parallelStream()
.unordered()
.collect(Collectors.toCollection(CopyOnWriteArrayList::new));
System.out.println(ordered);
System.out.println(result2);
我希望看到类似的结果,因为流是并行且无序的(也许 unordered()
是多余的,因为它已经是并行的)。但是,结果列表是有序的,即它等于源列表。
所以我的问题是为什么收集的列表是有序的? collect
是否始终遵循相遇顺序,即使对于并行的无序流也是如此?是特定的 Collectors.toCollection(...)
收集器强制遇到顺序吗?
最佳答案
Collectors.toCollection
返回缺少 Collector.Characteristics.UNORDERED
特征的 Collector
。另一个指定了 Collector.Characteristics.UNORDERED
的收集器可能会有不同的行为。
就是说:“无序”意味着没有保证,而不是保证变化。如果图书馆发现最容易将无序集合视为有序,则允许这样做,并且允许该行为在星期二或满月时更改版本。
(另请注意,如果您要使用并行流,Collectors.toCollection
不要求您使用并发集合实现;toCollection(ArrayList::new)
会工作得很好。那是因为收集器没有 Collector.Characteristics.CONCURRENT
特征,所以它使用一种收集策略,即使是并行流也适用于非并发收集。)
如果您使用无序流但收集器不是UNORDERED
,反之亦然,我怀疑您能否从框架获得任何保证。如果有一张 table ,它会说“HERE BE DRAGONS UNDEFINED BEHAVIOR”。我还期望这里不同类型的链式操作存在一些差异,例如Eugene 提到 findFirst
在这里有所不同,即使 findFirst
本质上是一个有序操作——unordered().findFirst()
变得等同于 findAny()
.
对于 Stream.collect
,我相信当前的实现有三种策略可供选择:
- 顺序:启动一个累加器,将元素累加到其中(按遇到的顺序,因为你为什么要费心去打乱元素?只需按照你得到它们的顺序接受它们),调用完成器。
- 并行执行,并发收集器,流或收集器是无序的:一个累加器,对输入进行分片,工作线程处理来自每个分片的元素,并在准备好时将元素添加到累加器,调用终结器。
- 并行执行,其他任何事情:将输入分成 N 个分片,每个分片依次累积到它自己的不同累加器中,累加器与组合器函数组合,调用终结器。
关于java - 遇到顺序友好/不友好的终端操作 vs 并行/顺序 vs 有序/无序流,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44954433/