了解Java 8和Java 9中的顺序流和并行流分裂器
关于劈开者的问题乍一看并不简单。
在流中, .parallel()
更改流处理的行为。 但是我期望顺序和并行流创建的分裂器是相同的。 例如, 在顺序流中,通常不会调用.trySplit()
,而在并行流中,它是为了将拆分分裂器移交给另一个线程。
stream.parallel().spliterator()
与stream.parallel().spliterator()
之间的差异:
-
它们可能具有不同的特征:
Stream.of(1L, 2L, 3L).limit(2); // ORDERED Stream.of(1L, 2L, 3L).limit(2).parallel(); // SUBSIZED, SIZED, ORDERED
这里讨论了另一个无意义的流分裂器特征策略(并行似乎更好地计算): 了解java 8和java 9中的深层分裂器特征
-
它们在使用
.trySplit()
分割方面可能有不同的行为:Stream.of(1L, 2L, 3L); // NON NULL Stream.of(1L, 2L, 3L).limit(2); // NULL Stream.of(1L, 2L, 3L).limit(2).parallel(); // NON NULL
为什么最后两个有不同的行为? 如果我愿意,为什么我不能拆分连续流? (例如,丢弃其中一个分割以进行快速处理可能很有用)。
-
将分裂器转换为流时的重大影响:
spliterator = Stream.of(1L, 2L, 3L).limit(2).spliterator(); stream = StreamSupport.stream(spliterator, true); // No parallel processing!
在这种情况下,spliterator是从顺序流创建的,它禁用了拆分function( .trySplit()
返回null)。 稍后,需要转换回流,该流不会受益于并行处理。 丢人现眼。
最大的问题:作为一种解决方法,在调用.spliterator()
之前始终将流转换为并行的主要影响是什么?
// Supports activation of parallel processing later public static Stream myOperation(Stream stream) { boolean isParallel = stream.isParallel(); Spliterator spliterator = stream.parallel().spliterator(); return StreamSupport.stream(new Spliterator() { // My implementation of the interface here (omitted for clarity) }, isParallel).onClose(stream::close); } // Now I have the option to use parallel processing when needed: myOperation(stream).skip(1).parallel()...
这不是分裂器的一般属性,而是仅包装封装流管道的分裂器。
当您在从spliterator生成并且没有链接操作的流上调用spliterator()
时,无论流parallel
状态如何,您都将获得可能支持或不支持trySplit
的源spliterator。
ArrayList list = new ArrayList<>(); Collections.addAll(list, "foo", "bar", "baz"); Spliterator sp1 = list.spliterator(), sp2=list.stream().spliterator(); // true System.out.println(sp1.getClass()==sp2.getClass()); // not null System.out.println(sp2.trySplit());
同样
Spliterator sp = Stream.of("foo", "bar", "baz").spliterator(); // not null System.out.println(sp.trySplit());
但是只要在调用spliterator()
之前链接操作,就会得到一个包含流管道的分裂器。 现在,可以实现执行相关操作的专用分裂器,如LimitSpliterator
或MappingSpliterator
,但这还没有完成,因为当其他终端操作没有时,将流转换回分裂器已被视为最后的手段适合,而不是高优先级的用例。 相反,您将始终获得单个实现类的实例,该实例类尝试将流管道实现的内部工作转换为spliterator API。
对于有状态操作,这可能是安静的,对于非SIZED
流,最明显的是,已sorted
, distinct
或skip
和limit
。 对于简单的无状态操作,比如map
或filter
,提供支持要容易得多,就像在代码注释中所说的那样
抽象包装spliterator,在第一次操作时绑定到管道助手的spliterator。 这个分裂器不是后期绑定的,并且在第一次操作时将绑定到源分裂器。 如果存在有状态操作,则不能拆分由顺序流生成的包裹分裂器。
… // @@@ Detect if stateful operations are present or not // If not then can split otherwise cannot /** * True if this spliterator supports splitting */ final boolean isParallel;
但似乎目前这种检测尚未实施,所有中间操作都被视为有状态操作。
Spliterator sp = Stream.of("foo", "bar", "baz").map(x -> x).spliterator(); // null System.out.println(sp.trySplit());
当您尝试通过始终调用parallel
来解决此问题时,当流管道仅包含无状态操作时,将不会产生任何影响。 但是当进行有状态操作时,它可能会显着改变行为。 例如,当您有一个已sorted
步骤时,所有元素都必须进行缓冲和排序,然后才能使用第一个元素。 对于并行流,它可能会使用parallelSort
,即使您从未调用trySplit
。