Come garantire l'ordine di elaborazione nei flussi java8?


148

Voglio elaborare liste all'interno di un XML oggetto Java. Devo garantire l'elaborazione di tutti gli elementi per averli ricevuti.

Devo quindi invocare sequentialciascunostream che utilizzo? list.stream().sequential().filter().forEach()

O è sufficiente usare il flusso solo se non uso il parallelismo? list.stream().filter().forEach()

Risposte:


339

Stai facendo la domanda sbagliata. Stai chiedendo di sequentialvs. parallelmentre vuoi elaborare gli articoli in ordine , quindi devi chiedere di ordinare . Se hai un ordine flusso ed esegui operazioni che garantiscono di mantenere l'ordine, non importa se il flusso viene elaborato in parallelo o in sequenza; l'implementazione manterrà l'ordine.

La proprietà ordinata è distinta dal parallelo rispetto al sequenziale. Ad esempio, se si chiama stream()in un HashSettorrente sarà ordinata durante la chiamata stream()su un Listrestituisce un flusso ordinato. Si noti che è possibile chiamare unordered()per rilasciare il contratto di ordinazione e potenzialmente aumentare le prestazioni. Se lo stream non ha alcun ordine, non è possibile ristabilire l'ordine. (L'unico modo per trasformare un flusso non ordinato in un ordine è quello di chiamare sorted, tuttavia, l'ordine risultante non è necessariamente l'ordine originale).

Vedi anche la sezione "Ordinazione" della java.util.streamdocumentazione del pacchetto .

Al fine di garantire la manutenzione dell'ordine durante un'intera operazione di flusso, è necessario studiare la documentazione della fonte del flusso, tutte le operazioni intermedie e l'operazione del terminale per verificare se mantengono l'ordine o meno (o se la fonte ha un ordine nel primo posto).

Questo può essere molto sottile, ad esempio Stream.iterate(T,UnaryOperator)crea uno stream ordinato mentre Stream.generate(Supplier)crea uno stream non ordinato . Si noti che anche hai fatto un errore comune nella sua domanda, come non lo faforEach mantiene l'ordine. Devi utilizzare forEachOrderedse desideri elaborare gli elementi del flusso in un ordine garantito.

Quindi se la listtua domanda è davvero a java.util.List, il suo stream()metodo restituirà un flusso ordinato efilter non cambierà l'ordinamento. Quindi, se chiami list.stream().filter() .forEachOrdered(), tutti gli elementi verranno elaborati in sequenza in ordine, mentre per list.parallelStream().filter().forEachOrdered()gli elementi potrebbero essere elaborati in parallelo (ad esempio dal filtro) ma l'azione terminale verrà comunque chiamata in ordine (il che ovviamente ridurrà il beneficio dell'esecuzione in parallelo) .

Se, ad esempio, usi un'operazione come

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

l'intera operazione potrebbe trarre vantaggio dall'esecuzione parallela, ma l'elenco risultante sarà sempre nell'ordine corretto, indipendentemente dal fatto che si usi un flusso parallelo o sequenziale.


48
Sì, buona risposta Una cosa che ho scoperto è che la terminologia che usiamo, almeno in inglese, come "prima", "dopo" e così via, è piuttosto ambigua. Esistono due tipi di ordinamento qui: 1) ordine di incontro (noto anche come ordine spaziale ) e 2) ordine di elaborazione (noto anche come ordine temporale ). Tenendo presente questa distinzione, può essere utile utilizzare parole come "sinistra di" o "destra di" quando si discute l'ordine dell'incontro e "prima di" o "dopo" quando si discute dell'ordine di elaborazione.
Stuart segna il

Capisco List<>che conserverà l'ordine, ma lo farà Collection<>?
Josh C.

5
@JoshC. dipende dal tipo di raccolta effettivo. Setdi solito no, a meno che non sia un SortedSeto LinkedHashSet. I punti di vista di raccolta di una Map( keySet(), entrySet(), e values()) ereditano la Map's politica, vale a dire sono ordinate quando la mappa è un SortedMapo LinkedHashMap. Il comportamento è determinato dalle caratteristiche segnalate dal divisore della raccolta . L' defaultimplementazione di Collectionnon riporta la ORDEREDcaratteristica, quindi non è ordinata, a meno che non venga ignorata.
Holger,

@Holger Ho avuto una domanda che potrebbe essere in qualche modo correlata a una piccola sezione della tua risposta.
Naman,

1
Vale la pena notare che forEachOrdereddifferisce solo forEachquando si usano flussi paralleli - ma buona pratica usarlo comunque quando si ordinano le cose nel caso in cui il metodo di cottura a vapore cambi mai ...
Steve Chambers

0

In breve:

L'ordinamento dipende dalla struttura dei dati di origine e dalle operazioni del flusso intermedio. Supponendo che tu stia utilizzando un Listprocesso dovrebbe essere ordinato (poiché qui filternon cambierà la sequenza).

Più dettagli:

Sequenziale vs Parallelo vs Non ordinato:

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.

Streaming Ordering:

javadocs

Gli stream possono avere o meno un ordine di incontro definito. Il fatto che uno stream abbia o meno un ordine di incontro dipende dalla sorgente e dalle operazioni intermedie. Alcune origini di stream (come List o array) sono intrinsecamente ordinate, mentre altre (come HashSet) non lo sono. Alcune operazioni intermedie, come sort (), possono imporre un ordine di incontro su un flusso altrimenti non ordinato, mentre altre possono rendere un flusso ordinato non ordinato, come BaseStream.unordered (). Inoltre, alcune operazioni del terminale potrebbero ignorare l'ordine degli incontri, come forEach ().

Se viene ordinato un flusso, la maggior parte delle operazioni è vincolata a operare sugli elementi nel loro ordine di incontro; se l'origine di uno stream è un Elenco contenente [1, 2, 3], il risultato dell'esecuzione della mappa (x -> x * 2) deve essere [2, 4, 6]. Tuttavia, se la sorgente non ha un ordine di incontro definito, qualsiasi permutazione dei valori [2, 4, 6] sarebbe un risultato valido.

Per i flussi sequenziali, la presenza o l'assenza di un ordine di incontro non influisce sulle prestazioni, ma solo sul determinismo. Se viene ordinato un flusso, l'esecuzione ripetuta di condutture di flusso identiche su una sorgente identica produrrà un risultato identico; se non viene ordinato, l'esecuzione ripetuta potrebbe produrre risultati diversi.

Per i flussi paralleli, il rilassamento del vincolo di ordinamento può a volte consentire un'esecuzione più efficiente. Alcune operazioni di aggregazione, come il filtraggio di duplicati (distinti ()) o riduzioni raggruppate (Collectors.groupingBy ()) possono essere implementate in modo più efficiente se l'ordine degli elementi non è rilevante. Allo stesso modo, operazioni intrinsecamente legate all'incontro con l'ordine, come limit (), possono richiedere il buffering per garantire un corretto ordinamento, minando il beneficio del parallelismo. Nei casi in cui lo stream ha un ordine di incontro, ma l'utente non si preoccupa particolarmente di quell'ordine di incontro, il riordinamento esplicito dello stream con unorder () può migliorare le prestazioni parallele per alcune operazioni stateful o terminali. Tuttavia, la maggior parte delle condotte di flusso, come l'esempio "somma di peso dei blocchi" sopra,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.