Chciałbym zduplikować strumień Java 8, abym mógł sobie z tym poradzić dwa razy. Mogę collect
jako listę i uzyskać z tego nowe strumienie;
// doSomething() returns a stream
List<A> thing = doSomething().collect(toList());
thing.stream()... // do stuff
thing.stream()... // do other stuff
Ale wydaje mi się, że powinien być bardziej wydajny / elegancki sposób.
Czy istnieje sposób na skopiowanie strumienia bez przekształcania go w zbiór?
W rzeczywistości pracuję ze strumieniem Either
s, więc chcę przetworzyć lewą projekcję w jeden sposób, zanim przejdę do prawej projekcji i zajmiemy się tym w inny sposób. Coś w tym rodzaju (z którym jak dotąd jestem zmuszony używać toList
sztuczki).
List<Either<Pair<A, Throwable>, A>> results = doSomething().collect(toList());
Stream<Pair<A, Throwable>> failures = results.stream().flatMap(either -> either.left());
failures.forEach(failure -> ... );
Stream<A> successes = results.stream().flatMap(either -> either.right());
successes.forEach(success -> ... );
java
lambda
java-8
java-stream
Toby
źródło
źródło
Odpowiedzi:
Myślę, że twoje założenia dotyczące wydajności są wsteczne. Ten ogromny zwrot z tytułu wydajności uzyskasz, jeśli zamierzasz wykorzystać dane tylko raz, ponieważ nie musisz ich przechowywać, a strumienie zapewniają potężne optymalizacje „fuzji pętli”, które pozwalają efektywnie przepływać całe dane przez potok.
Jeśli chcesz ponownie wykorzystać te same dane, z definicji musisz albo wygenerować je dwukrotnie (deterministycznie), albo zapisać. Jeśli zdarzy się, że znajduje się już w kolekcji, świetnie; następnie powtórzenie go dwukrotnie jest tanie.
Eksperymentowaliśmy w projekcie z „rozwidlonymi strumieniami”. Odkryliśmy, że wspieranie tego pociąga za sobą rzeczywiste koszty; obciążało to zwykły przypadek (użyj raz) kosztem rzadkiego przypadku. Dużym problemem było rozwiązanie tego, „co się dzieje, gdy dwa potoki nie zużywają danych w tym samym tempie”. Teraz i tak wrócisz do buforowania. To była cecha, która najwyraźniej nie miała swojej wagi.
Jeśli chcesz wielokrotnie operować na tych samych danych, zapisz je lub zorganizuj swoje operacje jako konsumenci i wykonaj następujące czynności:
Możesz również zajrzeć do biblioteki RxJava, ponieważ jej model przetwarzania lepiej nadaje się do tego rodzaju „rozwidlania strumienia”.
źródło
toList
), aby móc je przetworzyć (Either
przypadek jako przykład)?Możesz użyć zmiennej lokalnej z a,
Supplier
aby skonfigurować wspólne części potoku strumienia.Z http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ :
źródło
Supplier
jeśliStream
jest zbudowany w sposób „kosztowny”, płacisz ten koszt za każde połączenie doSupplier.get()
. tj. jeśli zapytanie do bazy danych ... to zapytanie jest wykonywane za każdym razemSet<Integer>
usingcollect(Collectors.toSet())
... i wykonać na tym kilka operacji. Chciałemmax()
i gdyby konkretna wartość była ustawiona jako dwie operacje ...filter(d -> d == -1).count() == 1;
Użyj a,
Supplier
aby utworzyć strumień dla każdej operacji kończenia.Zawsze, gdy potrzebujesz strumienia z tej kolekcji, użyj,
streamSupplier.get()
aby uzyskać nowy strumień.Przykłady:
streamSupplier.get().anyMatch(predicate);
streamSupplier.get().allMatch(predicate2);
źródło
Wdrożyliśmy
duplicate()
metodę dla strumieni w jOOλ , bibliotece Open Source, którą stworzyliśmy, aby usprawnić testowanie integracji dla jOOQ . Zasadniczo możesz po prostu napisać:Wewnętrznie istnieje bufor przechowujący wszystkie wartości, które zostały zużyte z jednego strumienia, ale nie z drugiego. Jest to prawdopodobnie tak wydajne, jak to tylko możliwe, jeśli twoje dwa strumienie są zużywane mniej więcej w tym samym tempie i jeśli możesz żyć z brakiem bezpieczeństwa wątków .
Oto jak działa algorytm:
Więcej kodu źródłowego tutaj
Tuple2
jest prawdopodobnie podobny do TwojegoPair
typu, aleSeq
maStream
pewne ulepszenia.źródło
Tuple2<Seq<A>>, Seq<A>> t = duplicate(stream); long count = t.collect(counting()); List<A> list = t.collect(toList());
, lepiej to zrobićTuple2<Long, List<A>> t = stream.collect(Tuple.collectors(counting(), toList()));
. UżycieCollectors.mapping/reducing
jednego może wyrazić inne operacje strumieniowe jako kolektory i przetwarzać elementy w zupełnie inny sposób, tworząc pojedynczą wynikową krotkę. Ogólnie rzecz biorąc, możesz zrobić wiele rzeczy, zużywając strumień raz bez powielania i będzie to przyjazne dla równoległych.offer()
/poll()
API, aleArrayDeque
może zrobić to samo.Możesz utworzyć strumień elementów wykonawczych (na przykład):
Gdzie
failure
isuccess
jakie operacje należy zastosować. Spowoduje to jednak utworzenie wielu obiektów tymczasowych i może nie być bardziej wydajne niż rozpoczęcie od kolekcji i dwukrotne przesyłanie strumieniowe / iterowanie.źródło
Innym sposobem wielokrotnego obsługiwania elementów jest użycie Stream.peek (Consumer) :
peek(Consumer)
można łączyć łańcuchami tyle razy, ile potrzeba.źródło
cyclops-react , biblioteka, do której się przyczyniam, ma statyczną metodę, która pozwoli ci zduplikować strumień (i zwróci krotkę strumieni jOOλ).
Zobacz komentarze, istnieje spadek wydajności, który zostanie naliczony podczas używania duplikatu w istniejącym strumieniu. Bardziej wydajną alternatywą byłoby użycie Streamable: -
Istnieje również (leniwa) klasa Streamable, którą można skonstruować na podstawie Stream, Iterable lub Array i wielokrotnie odtwarzać.
AsStreamable.synchronizedFromStream (stream) - może służyć do tworzenia Streamable, który będzie leniwie wypełniał swoją kolekcję zapasową w sposób, który może być współużytkowany przez wątki. Streamable.fromStream (stream) nie spowoduje żadnego obciążenia związanego z synchronizacją.
źródło
List<Integer> list = stream.collect(Collectors.toList()); streams = new Tuple2<>(list.stream(), list.stream())
(jak sugeruje OP). Ponadto, proszę wyraźnie ujawnić w odpowiedzi, że jesteś autorem cyklop-strumieni. Przeczytaj to .W przypadku tego konkretnego problemu możesz również użyć partycjonowania. Coś jak
źródło
Możemy skorzystać z Stream Builder podczas czytania lub iteracji strumienia. Oto dokument programu Stream Builder .
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.Builder.html
Przypadek użycia
Powiedzmy, że mamy strumień pracowników i musimy użyć tego strumienia, aby zapisać dane pracowników w pliku Excel, a następnie zaktualizować kolekcję / tabelę pracowników [To tylko przypadek użycia, aby pokazać użycie Stream Builder]:
źródło
Miałem podobny problem i mogłem wymyślić trzy różne struktury pośrednie, z których można utworzyć kopię strumienia: a
List
, tablicę iStream.Builder
. Napisałem mały program porównawczy, który sugerował, że z punktu widzenia wydajnościList
był o około 30% wolniejszy niż pozostałe dwa, które były dość podobne.Jedyną wadą konwersji na tablicę jest to, że jest to trudne, jeśli typ elementu jest typem ogólnym (który w moim przypadku był); dlatego wolę używać pliku
Stream.Builder
.Skończyło się na napisaniu małej funkcji, która tworzy
Collector
:Mogę wtedy zrobić kopię dowolnego strumienia
str
, robiąc to,str.collect(copyCollector())
co wydaje się być zgodne z idiomatycznym użyciem strumieni.źródło