4

乍一看并不直截了当的关于拆分器的问题。

在流中,.parallel()更改处理流的行为。但是,我期望从顺序流和并行流创建的拆分器是相同的。例如,通常在顺序流中,.trySplit()从不调用,而在并行流中,它是为了将拆分拆分器移交给另一个线程。

stream.spliterator()vs之间的区别stream.parallel().spliterator()

  1. 它们可能具有不同的特征:

    Stream.of(1L, 2L, 3L).limit(2);            // ORDERED
    Stream.of(1L, 2L, 3L).limit(2).parallel(); // SUBSIZED, SIZED, ORDERED
    

似乎这里讨论了另一个无意义的流拆分器特征策略(并行似乎更好地计算):Understanding deep spliterator features in java 8 and java 9

  1. 它们在使用拆分方面可能有不同的行为.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
    

为什么最后两个有不同的行为?如果我愿意,为什么我不能拆分顺序流?(例如,丢弃其中一个拆分以进行快速处理可能很有用)。

  1. 将拆分器转换为流时的重大影响:

    spliterator = Stream.of(1L, 2L, 3L).limit(2).spliterator();
    stream = StreamSupport.stream(spliterator, true); // No parallel processing!
    

在这种情况下,拆分器是从禁用拆分能力的顺序流创建的(.trySplit()返回 null)。稍后,需要转换回流时,该流将无法从并行处理中受益。丢人现眼。

最大的问题:作为一种解决方法,在调用之前始终将流转换为并行的主要影响是什么?.spliterator()

// Supports activation of parallel processing later
public static <T> Stream<T> myOperation(Stream<T> stream) {
    boolean isParallel = stream.isParallel();
    Spliterator<T> spliterator = stream.parallel().spliterator();
    return StreamSupport.stream(new Spliterator<T>() {
        // 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()...
4

1 回答 1

6

这不是拆分器的一般属性,而只是封装流管道的包装拆分器。

当您调用spliterator()从拆分器生成且没有链接操作的流时,您将获得可能支持或不支持的源拆分器trySplit,无论流parallel状态如何。

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "foo", "bar", "baz");
Spliterator<String> sp1 = list.spliterator(), sp2=list.stream().spliterator();
// true
System.out.println(sp1.getClass()==sp2.getClass());
// not null
System.out.println(sp2.trySplit());

同样地

Spliterator<String> sp = Stream.of("foo", "bar", "baz").spliterator();
// not null
System.out.println(sp.trySplit());

但是,只要在调用之前链接操作spliterator(),就会得到一个包装流管道的拆分器。现在,可以实现执行相关操作的专用拆分器,例如 aLimitSpliterator或 a MappingSpliterator,但这还没有完成,因为当其他终端操作不适合时,将流转换回拆分器被认为是最后的手段,不是高优先级的用例。相反,您将始终获得单个实现类的实例,该实例试图将流管道实现的内部工作转换为拆分器 API。

对于有状态的操作,这可能很复杂,尤其是对于非流的sorted,distinctskip& 。对于琐碎的无状态操作,例如or ,提供支持会容易得多,甚至在代码注释中也说过limitSIZEDmapfilter

抽象包装拆分器,在第一次操作时绑定到管道助手的拆分器。此拆分器不是后期绑定的,并且在首次操作时将绑定到源拆分器。如果存在有状态操作,则无法拆分从顺序流生成的包装拆分器。

…

   // @@@ 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<String> sp = Stream.of("foo", "bar", "baz").map(x -> x).spliterator();
// null
System.out.println(sp.trySplit());

当您尝试通过始终调用来解决此问题parallel时,当流管道仅包含无状态操作时不会产生影响。但是当有一个有状态的操作时,它可能会显着改变行为。例如,当您有一个sorted步骤时,必须先缓冲和排序所有元素,然后才能使用第一个元素。对于并行流,它可能会使用 a parallelSort,即使您从未调用trySplit.

于 2017-10-12T15:03:57.660 回答