受到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);
这些名单确实不同。 unordered
列表甚至会从一次运行更改为另一次运行,表明结果实际上是非确定性的。
所以我创建了另一个例子:
CopyOnWriteArrayList<Integer> result2 = ordered.parallelStream()
.unordered()
.collect(Collectors.toCollection(CopyOnWriteArrayList::new));
System.out.println(ordered);
System.out.println(result2);
我希望看到类似的结果,因为流是并行和无序的(可能unordered()
是冗余的,因为它已经是并行的)。但是,结果列表是有序的,即它等于源列表。
所以我的问题是为什么收集的清单是有序的? collect
是否总是尊重遭遇顺序,即使对于并行,无序的流也是如此?特定的Collectors.toCollection(...)
收集器是强制遭遇订单的收集器吗?
答案 0 :(得分:6)
Collectors.toCollection
会返回缺少Collector
特征的Collector.Characteristics.UNORDERED
。另一个指定Collector.Characteristics.UNORDERED
的收集器可能表现不同。
那说:&#34;无序&#34;表示无保证,而保证不会发生变化。如果库发现最简单的方法是按顺序处理无序集合,则允许这样做,并允许该行为在周二更换发布版本,或者如果有满月。
(另请注意,Collectors.toCollection
如果您要使用并行流,则不要求您使用并发集合实现; toCollection(ArrayList::new)
可以正常工作。这是因为收集器不具有Collector.Characteristics.CONCURRENT
特性,因此它使用一种集合策略,即使对于并行流也适用于非并发集合。)
如果您使用的是无序流,但收集器不是UNORDERED
,反之亦然,我怀疑您是否从框架中得到任何保证。如果有一张桌子,它会说&#34;这里 DRAGONS UNDEFINED BEAVAVIOR。&#34;我还希望在这里对不同类型的链式操作有一些区别,例如: Eugene提到findFirst
在这里有所不同,即使findFirst
本质上是有序操作 - unordered().findFirst()
也等同于findAny()
。
对于Stream.collect
,我认为目前的实施有三种策略:
答案 1 :(得分:4)
在当前实现下,我检查了java-8和java-9,对于非并发收集器collect
阶段忽略了无序标志(Collector.Characteristics.UNORDERED
没有设置)。允许实现这样做,以某种方式类似question。
在您链接的同一问题中,我确实提供了findFirst
实际如何从jdk-8更改为jdk-9的示例。
答案 2 :(得分:1)
文档Stream#collect已经提到过:
当并行执行时,多个中间结果可能实例化,填充和合并,以便保持可变数据结构的隔离。因此,即使与非线程安全的数据结构(例如ArrayList)并行执行,也不需要额外的同步来进行并行缩减。
这意味着Stream#collect
做了两件事:split
&amp; merge
。
但我在jdk-8中有一个特殊的例子,你可以获取不同的结果,:)。在Stream#generate创建无序流时,您可以在Collectors#toList
上获取不同的结果,例如:
Set<Set<Integer>> result = IntStream.range(0, 10).mapToObj(__ -> {
return unordered().parallel().collect(toSet());
}).collect(toSet());
assert result.each.size() == 100000; // ok
// v--- surprised, it was pass
assert result.size() > 1;
Stream<Integer> unordered() {
AtomicInteger counter = new AtomicInteger();
return Stream.generate(counter::getAndIncrement).limit(10000);
}