2015-07-01 9 views
9

Ich benutze den Stream-Spliterator direkt für die Low-Level-Operationen in der Bibliothek, die ich schreibe. Vor kurzem habe ich sehr seltsames Verhalten entdeckt, wenn ich den Stream spliterator nehme und tryAdvance/trySplit Aufrufe interleave. Hier ist ein einfacher Code, der das Problem demonstriert:Seltsames Verhalten von Stream.spliterator für parallele Ströme

import java.util.Arrays; 
import java.util.Spliterator; 

public class SpliteratorBug { 
    public static void main(String[] args) { 
     Integer[][] input = { { 1 }, { 2, 3 }, { 4, 5, 6 }, { 7, 8 }, { 9 } }; 
     Spliterator<Integer> spliterator = Arrays.stream(input).parallel() 
       .flatMap(Arrays::stream).spliterator(); 
     spliterator.trySplit(); 
     spliterator.tryAdvance(s -> {}); 
     spliterator.trySplit(); 
     spliterator.forEachRemaining(System.out::println); 
    } 
} 

Der Ausgang ist

5 
6 
9 

Wie Sie sehen können, nach dem Flat-Mapping I sollte den geordneten Strom von aufeinanderfolgenden Zahlen 1-9 zu bekommen. Ich spalte den Spliterator einmal, also sollte er an einen Zwischenplatz springen. Als nächstes verzehre ich ein Element daraus und spalte es noch einmal. Danach drucke ich alle restlichen Elemente. Ich erwarte, dass ich mehrere aufeinanderfolgende Elemente aus dem Stream Tail haben werde (wahrscheinlich null Elemente, es wäre auch in Ordnung). Was ich jedoch bekomme, ist 5 und 6, dann plötzlich auf 9 springen.

Ich weiß, dass derzeit in JDK-Spliteratoren nicht auf diese Weise verwendet werden: Sie teilen immer vor dem Durchlauf. Jedoch verbietet es der Beamte documentation nicht ausdrücklich, die trySplit nach tryAdvance anzurufen.

Das Problem wurde nie beobachtet, wenn ich Spliterator direkt aus Sammlung, Array, generierte Quelle usw. verwendet. Es wird nur beobachtet, wenn der Spliterator aus dem parallelen Strom, der das Zwischenprodukt flatMap hatte, erstellt wurde.

Die Frage ist also: Habe ich den Fehler gefunden oder ist es explizit verboten, den Spliterator auf diese Weise zu verwenden?

Antwort

3

Von dem, was ich von der Quelle der AbstractWrappingSpliterator und Unternehmen sehen kann, wenn Sie tryAdvance, die Ausgabe von flatMap (4,5,6) gepuffert werden und dann 4 werden verlassen verbraucht (5,6) im Puffer. Dann spaltet trySplit korrekt (7,8) auf die neue Spliterator ab, wobei 9 in der alten bleibt, aber die gepufferten (5,6) bleiben bei der alten Spliterator.

So sieht das wie ein Fehler für mich aus. Er sollte entweder den Puffer an die neue Spliterator abgeben oder null zurückgeben und die Aufteilung verweigern, wenn der Puffer nicht leer ist.

2

Aus der Dokumentation von Spliterator.trySplit():

Diese Methode null aus irgendeinem Grund zurückgeben kann, einschließlich Leere, Unfähigkeit nach aufzuspalten Traversal, Datenstruktur Einschränkungen begonnen hat, und Effizienzüberlegungen.

(Hervorhebung von mir)

So ist die Dokumentation erwähnt ausdrücklich die Möglichkeit Splitting zu versuchen, nach Überquerung beginnt und legt nahe, dass spliterators, die zu handhaben nicht in der Lage sind, können null zurück.

Also für geordnete Spliteratoren sollte das beobachtete Verhalten einen Fehler as described by Misha berücksichtigt werden.Im Allgemeinen ist die Tatsache, dass trySplit() einen Präfix spliterator zurückgeben muss, mit anderen Worten, alle Zwischenzustände in Bezug auf die nächsten Elemente an den neuen Spliterator übergeben, ist eine Besonderheit der Spliterator API, die Bugs wahrscheinlich macht. Ich nahm diese Frage als ein Motiv für die Überprüfung meiner eigenen Spliterator-Implementierungen und fand einen ähnlichen Fehler ...

1

Dieses Verhalten wurde offiziell als Fehler erkannt (siehe JDK-8148838), von mir behoben und in JDK-9-Stamm geschoben (siehe changeset) . Die traurige Sache ist, dass mein ursprünglicher Patch tatsächlich die Aufspaltung nach flatMap (siehe webrev) reparierte, aber dieser Patch wurde abgelehnt, da ein solches Szenario (unter Verwendung von trySplit() nach tryAdvance()) als ungewöhnlich und entmutigt angesehen wurde. Die derzeit akzeptierte Lösung besteht darin, die WrappingSpliterator Aufteilung nach dem Vorlauf zu deaktivieren, was ausreicht, um das Problem zu beheben.