Jak zapewnić kolejność przetwarzania w strumieniach java8?

148

Chcę przetwarzać listy wewnątrz XMLobiektu Java. Muszę zadbać o przetworzenie wszystkich elementów w celu ich otrzymania.

Czy powinienem zatem dzwonić sequentialdo każdego, streamktórego używam? list.stream().sequential().filter().forEach()

Czy wystarczy po prostu użyć strumienia, o ile nie używam równoległości? list.stream().filter().forEach()

Membersound
źródło

Odpowiedzi:

338

Zadajesz złe pytanie. Pytasz o sequentiala parallelpodczas gdy chcesz przetwarzać pozycje w kolejności , więc musisz zapytać o zamówienie . Jeśli masz uporządkowany strumień i wykonujesz operacje gwarantujące utrzymanie kolejności, nie ma znaczenia, czy strumień jest przetwarzany równolegle czy sekwencyjnie; realizacja utrzyma porządek.

Uporządkowana właściwość różni się od równoległej i sekwencyjnej. Np jeśli zadzwonisz stream()na zasadzie HashSetstrumień będzie nieuporządkowana podczas wywoływania stream()na ciągu Listzwraca nakazał strumienia. Pamiętaj, że możesz zadzwonić, unordered()aby zwolnić zamówienie i potencjalnie zwiększyć wydajność. Gdy strumień nie ma już uporządkowania, nie ma możliwości przywrócenia kolejności. (Jedynym sposobem na przekształcenie nieuporządkowanego strumienia w uporządkowany jest wywołanie sorted, jednak wynikowa kolejność niekoniecznie jest pierwotną kolejnością).

Zobacz także sekcję „Zamawianie” w java.util.streamdokumentacji pakietu .

Aby zapewnić utrzymanie porządku w całej operacji strumienia, musisz przestudiować dokumentację źródła strumienia, wszystkie operacje pośrednie i działanie terminala pod kątem tego, czy utrzymują porządek, czy nie (lub czy źródło ma uporządkowanie w pierwszym miejsce).

Może to być bardzo subtelne, np. Stream.iterate(T,UnaryOperator)Tworzy uporządkowany strumień, podczas gdy Stream.generate(Supplier)tworzy nieuporządkowany strumień. Zwróć uwagę, że popełniłeś również częsty błąd w swoim pytaniu, ponieważ nie utrzymuje kolejności. Musisz użyć, jeśli chcesz przetwarzać elementy strumienia w gwarantowanej kolejności.forEach forEachOrdered

Więc jeśli twoje listpytanie rzeczywiście ma wartość a java.util.List, jego stream()metoda zwróci uporządkowany strumień i filternie zmieni kolejności. Więc jeśli wywołasz list.stream().filter() .forEachOrdered(), wszystkie elementy będą przetwarzane sekwencyjnie w kolejności, podczas gdy dla list.parallelStream().filter().forEachOrdered()elementów mogą być przetwarzane równolegle (np. Przez filtr), ale akcja terminala będzie nadal wywoływana w kolejności (co oczywiście zmniejszy korzyści z równoległego wykonania) .

Jeśli na przykład używasz operacji takiej jak

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

cała operacja może skorzystać na równoległym wykonaniu, ale wynikowa lista zawsze będzie we właściwej kolejności, niezależnie od tego, czy używasz strumienia równoległego, czy sekwencyjnego.

Holger
źródło
48
Tak, dobra odpowiedź. Jedną z rzeczy, które odkryłem, jest to, że terminologia, której używamy, przynajmniej w języku angielskim, jak „przed”, „po” i tak dalej, jest dość niejednoznaczna. Istnieją dwa rodzaje porządkowania: 1) porządek spotkań (znany również jako porządek przestrzenny ) i 2) porządek przetwarzania (nazywany również porządkiem czasowym ). Mając na uwadze to rozróżnienie, pomocne może być użycie słów takich jak „na lewo od” lub „na prawo od” podczas omawiania kolejności spotkań oraz „wcześniej niż” lub „później niż” podczas omawiania kolejności przetwarzania.
Stuart Marks
Rozumiem, List<>że zachowam porządek, ale czy tak Collection<>?
Josh C.
5
@JoshC. zależy to od rzeczywistego typu kolekcji. SetZwykle nie, chyba że jest to SortedSetlub LinkedHashSet. Widoki kolekcji Map( keySet(), entrySet()i values()) dziedziczą Mapzasady, tj. Są uporządkowane, gdy mapa jest SortedMaplub LinkedHashMap. Zachowanie jest określane przez cechy zgłaszane przez rozdzielacz kolekcji . defaultRealizacja Collectionnie zgłaszać ORDEREDcharakterystykę, więc jest to nieuporządkowana, chyba że zostaną zamienione.
Holger
@Holger Miałem pytanie, które może być nieco związane z małą częścią Twojej odpowiedzi.
Naman
1
Warto zauważyć, że forEachOrderedróżni się to tylko od forEachkorzystania ze strumieni równoległych - ale dobra praktyka, aby używać go mimo wszystko podczas zamawiania, na wypadek gdyby metoda gotowania na parze kiedykolwiek się zmieniła ...
Steve Chambers
0

W skrócie:

Kolejność zależy od struktury danych źródłowych i operacji na strumieniu pośrednim. Zakładając, że używasz a Listprzetwarzanie powinno zostać zamówione (ponieważ filternie zmieni tutaj kolejności).

Więcej szczegółów:

Sekwencyjne vs równoległe vs nieuporządkowane:

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.

Zamawianie strumienia:

Javadocs

Strumienie mogą mieć określoną kolejność spotkań lub nie. To, czy strumień ma kolejność spotkań, zależy od źródła i operacji pośrednich. Niektóre źródła strumieni (takie jak Lista lub tablice) są wewnętrznie uporządkowane, podczas gdy inne (takie jak HashSet) nie. Niektóre operacje pośrednie, takie jak sortowane (), mogą narzucać kolejność spotkań w nieuporządkowanym strumieniu, a inne mogą renderować uporządkowany strumień jako nieuporządkowany, na przykład BaseStream.unordered (). Co więcej, niektóre operacje terminalowe mogą ignorować kolejność napotkania, takie jak forEach ().

Jeśli strumień jest uporządkowany, większość operacji ogranicza się do działania na elementach w kolejności ich napotkania; jeśli źródłem strumienia jest lista zawierająca [1, 2, 3], to wynikiem wykonania mapy (x -> x * 2) musi być [2, 4, 6]. Jeśli jednak źródło nie ma zdefiniowanej kolejności spotkań, to każda permutacja wartości [2, 4, 6] byłaby prawidłowym wynikiem.

W przypadku strumieni sekwencyjnych obecność lub brak kolejności spotkań nie wpływa na wydajność, a jedynie na determinizm. Jeśli strumień jest uporządkowany, powtórne wykonanie identycznych potoków strumienia na identycznym źródle da identyczny wynik; jeśli nie zostanie zamówione, powtórne wykonanie może dać inne wyniki.

W przypadku strumieni równoległych złagodzenie ograniczenia porządkowania może czasami umożliwić bardziej wydajne wykonanie. Niektóre operacje zagregowane, takie jak filtrowanie duplikatów (odrębne ()) lub grupowane redukcje (Collectors.groupingBy ()), można zaimplementować wydajniej, jeśli kolejność elementów nie ma znaczenia. Podobnie operacje, które są nieodłącznie związane z napotkaniem porządku, takie jak limit (), mogą wymagać buforowania w celu zapewnienia właściwej kolejności, co podważa korzyści z równoległości. W przypadkach, gdy strumień ma kolejność spotkań, ale użytkownik nie dba szczególnie o kolejność spotkań, jawne usunięcie kolejności strumienia za pomocą unordered () może poprawić wydajność równoległą dla niektórych operacji stanowych lub terminalowych. Jednak większość potoków strumieniowych, na przykład w powyższym przykładzie „suma wagi bloków”,

Saikat
źródło