Właśnie natknąłem się na pytanie, kiedy używam a List
i jego stream()
metody. Chociaż wiem, jak ich używać, nie jestem pewien, kiedy ich używać.
Na przykład mam listę zawierającą różne ścieżki do różnych lokalizacji. Teraz chciałbym sprawdzić, czy pojedyncza podana ścieżka zawiera którąkolwiek ze ścieżek określonych na liście. Chciałbym zwrócićboolean
podstawie tego, czy warunek został spełniony.
Oczywiście nie jest to trudne zadanie samo w sobie. Ale zastanawiam się, czy powinienem używać strumieni, czy pętli for (-each).
Lista
private static final List<String> EXCLUDE_PATHS = Arrays.asList(new String[]{
"my/path/one",
"my/path/two"
});
Przykład - Stream
private boolean isExcluded(String path){
return EXCLUDE_PATHS.stream()
.map(String::toLowerCase)
.filter(path::contains)
.collect(Collectors.toList())
.size() > 0;
}
Przykład - pętla For-Each
private boolean isExcluded(String path){
for (String excludePath : EXCLUDE_PATHS) {
if(path.contains(excludePath.toLowerCase())){
return true;
}
}
return false;
}
Zwróć uwagę, że path
parametr jest zawsze pisany małymi literami .
Moje pierwsze przypuszczenie jest takie, że podejście for-each jest szybsze, ponieważ pętla powróci natychmiast, jeśli warunek zostanie spełniony. Podczas gdy strumień nadal będzie przechodził przez wszystkie wpisy na liście, aby zakończyć filtrowanie.
Czy moje założenie jest prawidłowe? Jeśli tak, dlaczego (a raczej kiedy ) miałbym stream()
wtedy użyć ?
źródło
new String[]{…}
. Po prostu użyjArrays.asList("my/path/one", "my/path/two")
String[]
, nie ma potrzeby dzwonićArrays.asList
. Możesz po prostu przesyłać strumieniowo przez tablicę za pomocąArrays.stream(array)
. Nawiasem mówiąc, mam trudności ze zrozumieniem ogólnego celuisExcluded
testu. Czy to naprawdę interesujące, czy elementEXCLUDE_PATHS
jest dosłownie zawarty gdzieś na ścieżce? To znaczyisExcluded("my/path/one/foo/bar/baz")
wrócętrue
, a takżeisExcluded("foo/bar/baz/my/path/one/")
…Arrays.stream
metody, dzięki za wskazanie tego. Rzeczywiście, przykład, który opublikowałem, wydaje się zupełnie bezużyteczny dla nikogo poza mną. Zdaję sobie sprawę z zachowaniaisExcluded
metody, ale tak naprawdę jest to coś, czego dla siebie potrzebuję, więc odpowiadając na Twoje pytanie: tak , jest ciekawa z powodów, o których nie chciałbym mówić, bo nie mieściłaby się w zakresie pierwotnego pytania.toLowerCase
stosuje się do stałej, która jest już mała? Czy nie należy go zastosować dopath
argumentu?Odpowiedzi:
Twoje założenie jest poprawne. Twoja implementacja strumienia jest wolniejsza niż pętla for.
To użycie strumienia powinno być jednak tak szybkie jak pętla for:
Powoduje to iterację przez elementy, stosowanie
String::toLowerCase
i filtrowanie elementów jeden po drugim i kończenie na pierwszym elemencie pasującym .Obie
collect()
&anyMatch()
są operacjami terminalowymi.anyMatch()
wychodzi jednak przy pierwszym znalezionym elemencie, podczas gdycollect()
wymaga przetworzenia wszystkich elementów.źródło
findFirst()
w połączeniu zfilter()
. Najwyraźniej nie umiem korzystać ze strumieni tak dobrze, jak myślałem.Decyzja, czy użyć strumieni, czy nie, nie powinna być podyktowana rozważaniami dotyczącymi wydajności, ale raczej czytelnością. Jeśli chodzi o wydajność, należy wziąć pod uwagę inne kwestie.
Swoim
.filter(path::contains).collect(Collectors.toList()).size() > 0
podejściem przetwarzasz wszystkie elementy i zbierasz je w tymczasoweList
, zanim porównasz rozmiar, jednak rzadko ma to znaczenie dla strumienia składającego się z dwóch elementów.Używanie
.map(String::toLowerCase).anyMatch(path::contains)
może zaoszczędzić cykle procesora i pamięć, jeśli masz znacznie większą liczbę elementów. Mimo to konwertuje to każdąString
na reprezentację małych liter, dopóki nie zostanie znalezione dopasowanie. Oczywiście jest sens używaniazamiast. Nie musisz więc powtarzać konwersji na małe litery przy każdym wywołaniu
isExcluded
. Jeśli liczba elementówEXCLUDE_PATHS
lub długości ciągów stają się naprawdę duże, możesz rozważyć użycieSkompilowanie łańcucha jako wzorca wyrażenia regularnego z
LITERAL
flagą sprawia, że zachowuje się on jak zwykłe operacje na łańcuchach, ale pozwala silnikowi spędzić trochę czasu na przygotowaniach, np. Przy użyciu algorytmu Boyera Moore'a, aby być bardziej wydajnym, jeśli chodzi o faktyczne porównanie.Oczywiście opłaca się to tylko wtedy, gdy jest wystarczająco dużo kolejnych testów, aby zrekompensować czas spędzony na przygotowaniach. Ustalenie, czy tak się stanie, jest jednym z faktycznych rozważań dotyczących wydajności, poza pierwszym pytaniem, czy ta operacja kiedykolwiek będzie miała krytyczne znaczenie dla wydajności. Nie chodzi o to, czy używać strumieni, czy
for
pętli.Nawiasem mówiąc, powyższe przykłady kodu zachowują logikę oryginalnego kodu, który wydaje mi się wątpliwy. Twoja
isExcluded
metoda zwracatrue
, jeśli określona ścieżka zawiera którykolwiek z elementów na liście, więc wracatrue
dla/some/prefix/to/my/path/one
, a takżemy/path/one/and/some/suffix
lub nawet/some/prefix/to/my/path/one/and/some/suffix
.Nawet
dummy/path/onerous
jest uważany za spełniający kryteria, ponieważ jestcontains
ciągiemmy/path/one
…źródło
Tak. Masz rację. Twoje podejście do transmisji będzie miało pewne obciążenie. Ale możesz użyć takiej konstrukcji:
Głównym powodem korzystania ze strumieni jest to, że sprawiają, że kod jest prostszy i łatwiejszy do odczytania.
źródło
anyMatch
jest skrótfilter(...).findFirst().isPresent()
?Celem strumieni w Javie jest uproszczenie pisania kodu równoległego. Inspiruje się programowaniem funkcjonalnym. Strumień szeregowy służy tylko do czyszczenia kodu.
Jeśli zależy nam na wydajności, powinniśmy użyć parallelStream, do którego został zaprojektowany. Szeregowy jest generalnie wolniejszy.
Jest dobry artykuł czytać o , a wydajność
ForLoop
Stream
ParallelStream
.W Twoim kodzie możemy użyć metod zakończenia, aby zatrzymać wyszukiwanie na pierwszym dopasowaniu. (anyMatch ...)
źródło
Jak inni wspominali o wielu dobrych punktach, ale ja chcę tylko wspomnieć o leniwej ocenie w ocenie strumienia. Kiedy
map()
tworzymy strumień ścieżek z małymi literami, nie tworzymy od razu całego strumienia, zamiast tego strumień jest konstruowany leniwie , dlatego wydajność powinna być równoważna tradycyjnej pętli for. Nie wykonuje pełnego skanowaniamap()
ianyMatch()
jest wykonywany w tym samym czasie. GdyanyMatch()
zwróci prawdę, nastąpi zwarcie.źródło