Wie kann die Reihenfolge der Verarbeitung in Java8-Streams sichergestellt werden?

148

Ich möchte Listen in einem XMLJava-Objekt verarbeiten. Ich muss sicherstellen, dass alle Elemente verarbeitet werden, damit ich sie erhalten habe.

Soll ich deshalb sequentialjeden anrufen, den streamich benutze? list.stream().sequential().filter().forEach()

Oder reicht es aus, nur den Stream zu verwenden, solange ich keine Parallelität verwende? list.stream().filter().forEach()

Mitgliedssound
quelle

Antworten:

337

Sie stellen die falsche Frage. Sie fragen nach sequentialvs., parallelwährend Sie Artikel in der richtigen Reihenfolge bearbeiten möchten , also müssen Sie nach der Bestellung fragen . Wenn Sie einen bestellten Stream haben und Vorgänge ausführen, die die Aufrechterhaltung der Reihenfolge gewährleisten, spielt es keine Rolle, ob der Stream parallel oder sequentiell verarbeitet wird. Die Implementierung wird die Reihenfolge beibehalten.

Die geordnete Eigenschaft unterscheidet sich von parallel und sequentiell. Zum Beispiel , wenn Sie rufen stream()auf einen HashSetwird der Strom ungeordneten seine beim Aufruf stream()auf einer ListRückkehr eines geordnete Strom. Beachten Sie, dass Sie anrufen können unordered(), um den Bestellvertrag freizugeben und möglicherweise die Leistung zu steigern. Sobald der Stream keine Bestellung mehr hat, kann die Bestellung nicht mehr wiederhergestellt werden. (Die einzige Möglichkeit, einen ungeordneten Stream in einen geordneten zu verwandeln, besteht darin, aufzurufen sorted. Die resultierende Bestellung ist jedoch nicht unbedingt die ursprüngliche Bestellung.)

Siehe auch den Abschnitt „Bestellung“ in der java.util.streamPaketdokumentation .

Um die Aufrechterhaltung der Bestellung während eines gesamten Stream-Vorgangs sicherzustellen, müssen Sie die Dokumentation der Stream-Quelle, aller Zwischenvorgänge und des Terminal-Vorgangs dahingehend untersuchen, ob sie den Auftrag aufrechterhalten oder nicht (oder ob die Quelle im ersten einen Auftrag hat Ort).

Dies kann sehr subtil sein, z. B. Stream.iterate(T,UnaryOperator)wird ein geordneter Stream Stream.generate(Supplier)erstellt, während ein ungeordneter Stream erstellt wird. Beachten Sie, dass Sie in Ihrer Frage auch einen häufigen Fehler gemacht haben, da die Bestellung nicht beibehalten wird. Sie müssen verwenden, wenn Sie die Elemente des Streams in einer garantierten Reihenfolge verarbeiten möchten.forEach forEachOrdered

Wenn es sich listbei Ihrer Frage also tatsächlich um eine handelt java.util.List, gibt die stream()Methode einen bestellten Stream zurück und filterändert die Reihenfolge nicht. Wenn Sie also aufrufen list.stream().filter() .forEachOrdered(), werden alle Elemente der Reihe nach nacheinander verarbeitet, während list.parallelStream().filter().forEachOrdered()die Elemente möglicherweise parallel verarbeitet werden (z. B. durch den Filter), die Terminalaktion jedoch weiterhin in der Reihenfolge aufgerufen wird (was offensichtlich den Vorteil der parallelen Ausführung verringert). .

Wenn Sie beispielsweise eine Operation wie verwenden

List<…> result=inputList.parallelStream().map(…).filter(…).collect(Collectors.toList());

Der gesamte Vorgang kann von einer parallelen Ausführung profitieren, die resultierende Liste ist jedoch immer in der richtigen Reihenfolge, unabhängig davon, ob Sie einen parallelen oder einen sequentiellen Stream verwenden.

Holger
quelle
48
Ja, gute Antwort. Eine Sache, die ich gefunden habe, ist, dass die Terminologie, die wir zumindest auf Englisch verwenden, wie "vorher", "nachher" usw., ziemlich mehrdeutig ist. Hier gibt es zwei Arten der Reihenfolge: 1) Begegnungsreihenfolge (auch als räumliche Reihenfolge bezeichnet ) und 2) Verarbeitungsreihenfolge (auch als zeitliche Reihenfolge bezeichnet ). In Anbetracht dieser Unterscheidung kann es hilfreich sein, Wörter wie "links von" oder "rechts von" zu verwenden, wenn die Reihenfolge der Begegnung besprochen wird, und "früher als" oder "später als", wenn die Reihenfolge der Verarbeitung besprochen wird.
Stuart Marks
Ich verstehe, List<>wird die Ordnung erhalten, aber wird Collection<>?
Josh C.
5
@ JoshC. Dies hängt vom tatsächlichen Sammlungstyp ab. Sets normalerweise nicht, es sei denn, es ist ein SortedSetoder LinkedHashSet. Die Sammlung Ansichten eines Map( keySet(), entrySet(), und values()) erbt die MapPolitik s‘, das heißt geordnet , wenn die Karte eine ist SortedMapoder LinkedHashMap. Das Verhalten wird durch die vom Spliterator der Sammlung gemeldeten Merkmale bestimmt . Die defaultImplementierung von Collectionmeldet das ORDEREDMerkmal nicht, daher ist es ungeordnet, sofern es nicht überschrieben wird.
Holger
@Holger Ich hatte eine Frage , die etwas mit einem kleinen Teil Ihrer Antwort zu tun haben könnte.
Naman
1
Es ist erwähnenswert, dass sich dies forEachOrderednur von der forEachVerwendung paralleler Streams unterscheidet. Es empfiehlt sich jedoch, diese bei der Bestellung zu verwenden, falls sich die Dampfmethode jemals ändert ...
Steve Chambers
0

In einer Nussschale:

Die Reihenfolge hängt von der Quelldatenstruktur und den Zwischenstromoperationen ab. Angenommen, Sie verwenden eine, sollte Listdie Verarbeitung bestellt werden (da filterhier die Reihenfolge nicht geändert wird).

Mehr Details:

Sequentiell vs Parallel vs Ungeordnet:

Javadocs

S sequential()
Returns an equivalent stream that is sequential. May return itself, either because the stream was already sequential, or because the underlying stream state was modified to be sequential.
This is an intermediate operation.
S parallel()
Returns an equivalent stream that is parallel. May return itself, either because the stream was already parallel, or because the underlying stream state was modified to be parallel.
This is an intermediate operation.
S unordered()
Returns an equivalent stream that is unordered. May return itself, either because the stream was already unordered, or because the underlying stream state was modified to be unordered.
This is an intermediate operation.

Stream-Bestellung:

Javadocs

Streams können eine definierte Begegnungsreihenfolge haben oder nicht. Ob ein Stream eine Begegnungsreihenfolge hat oder nicht, hängt von der Quelle und den Zwischenoperationen ab. Bestimmte Stream-Quellen (wie List oder Arrays) sind in sich geordnet, andere (wie HashSet) nicht. Einige Zwischenoperationen, wie z. B. sortiert (), können einem ansonsten ungeordneten Stream eine Begegnungsreihenfolge auferlegen, und andere können einen geordneten Stream ungeordnet machen, wie z. B. BaseStream.unordered (). Darüber hinaus können einige Terminaloperationen die Reihenfolge der Begegnungen ignorieren, z. B. forEach ().

Wenn ein Stream geordnet ist, müssen die meisten Operationen die Elemente in ihrer Begegnungsreihenfolge bearbeiten. Wenn die Quelle eines Streams eine Liste ist, die [1, 2, 3] enthält, muss das Ergebnis der Ausführung der Karte (x -> x * 2) [2, 4, 6] sein. Wenn die Quelle jedoch keine definierte Begegnungsreihenfolge hat, wäre jede Permutation der Werte [2, 4, 6] ein gültiges Ergebnis.

Bei sequentiellen Streams wirkt sich das Vorhandensein oder Fehlen einer Begegnungsreihenfolge nicht auf die Leistung aus, sondern nur auf den Determinismus. Wenn ein Stream bestellt wird, führt die wiederholte Ausführung identischer Stream-Pipelines auf einer identischen Quelle zu einem identischen Ergebnis. Wenn es nicht bestellt wird, kann eine wiederholte Ausführung zu unterschiedlichen Ergebnissen führen.

Bei parallelen Streams kann das Lockern der Ordnungsbeschränkung manchmal eine effizientere Ausführung ermöglichen. Bestimmte Aggregatoperationen, z. B. das Filtern von Duplikaten (eindeutige ()) oder gruppierte Reduzierungen (Collectors.groupingBy ()), können effizienter implementiert werden, wenn die Reihenfolge der Elemente nicht relevant ist. In ähnlicher Weise erfordern Operationen, die eng mit der Reihenfolge verbunden sind, wie z. B. limit (), möglicherweise eine Pufferung, um eine ordnungsgemäße Reihenfolge sicherzustellen, was den Vorteil der Parallelität untergräbt. In Fällen, in denen der Stream eine Begegnungsreihenfolge hat, der Benutzer sich jedoch nicht besonders um diese Begegnungsreihenfolge kümmert, kann die explizite Entordnung des Streams mit unordered () die parallele Leistung für einige Stateful- oder Terminal-Operationen verbessern. Die meisten Stream-Pipelines, wie das obige Beispiel "Summe des Gewichts von Blöcken",

Saikat
quelle