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
    

ここで説明されている別のナンセンスなストリームスプリッター特性ポリシー(並行して計算された方が良いようです)のようです:Java 8および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
    

最後の 2 つの動作が異なるのはなぜですか? シーケンシャル ストリームを分割したいのに分割できないのはなぜですか? (たとえば、高速処理のために分割の 1 つを破棄すると便利な場合があります)。

  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()サポートしている場合とサポートしていない場合があるソース スプリッテレータを取得します。trySplitparallel

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 に変換しようとする単一の実装クラスのインスタンスを常に取得します。

これは、ステートフルな操作の場合、非常に複雑になる可能性があります。最も顕著なのは、非ストリームのsorteddistinctまたはskip&です。またはのような些細なステートレス操作の場合、コード コメントでさえ指摘されているように、サポートを提供する方がはるかに簡単です。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ステップがある場合、最初の要素を使用する前に、すべての要素をバッファリングしてソートする必要があります。parallelSort並列ストリームの場合、 を呼び出さない場合でも、 を使用する可能性がありますtrySplit

于 2017-10-12T15:03:57.660 に答える