Akışları ne zaman kullanmalıyım?


99

A Listve stream()yöntemini kullanırken bir soru ile karşılaştım . Biliyorum iken nasıl bunları kullanmak için, ben pek emin değilim zaman bunları kullanmak.

Örneğin, farklı konumlara giden çeşitli yolları içeren bir listem var. Şimdi, verilen tek bir yolun listede belirtilen yollardan herhangi birini içerip içermediğini kontrol etmek istiyorum. Koşulun booleankarşılanıp karşılanmadığına bağlı olarak bir döndürmek istiyorum .

Bu elbette kendi başına zor bir görev değil. Ama akarsu mu yoksa for (-each) döngüsü mü kullanmalıyım merak ediyorum.

Liste

private static final List<String> EXCLUDE_PATHS = Arrays.asList(new String[]{
    "my/path/one",
    "my/path/two"
});

Örnek - Akış

private boolean isExcluded(String path){
    return EXCLUDE_PATHS.stream()
                        .map(String::toLowerCase)
                        .filter(path::contains)
                        .collect(Collectors.toList())
                        .size() > 0;
}

Örnek - Her Döngü İçin

private boolean isExcluded(String path){
    for (String excludePath : EXCLUDE_PATHS) {
        if(path.contains(excludePath.toLowerCase())){
            return true;
        }
    }
    return false;
}

Not o pathparametre her zaman olduğu küçük harf .

İlk tahminim, her biri için yaklaşımın daha hızlı olduğu, çünkü koşul karşılanırsa döngü hemen geri dönecektir. Oysa akış, filtrelemeyi tamamlamak için tüm liste girişleri üzerinde döngü yapacaktır.

Varsayımım doğru mu? Öyleyse, neden (veya daha doğrusu ne zaman ) kullanmalıyım stream()?


11
Akışlar, geleneksel döngülere göre daha etkileyici ve okunabilirdir. Daha sonra, eğer-o zaman ve koşullar vb. İçsel konular hakkında dikkatli olmanız gerekir. Akış ifadesi çok açıktır: dosya adlarını küçük harflere çevirin, sonra bir şeye göre filtreleyin ve sonra sayın, toplayın vb. Sonuç: çok yinelemeli hesaplama akışının ifadesi.
Jean-Baptiste Yunès

12
Burada gerek new String[]{…}yok. Sadece kullanınArrays.asList("my/path/one", "my/path/two")
Holger

4
Kaynağınız bir ise String[], aramanıza gerek yoktur Arrays.asList. Kullanarak dizi üzerinden yayın yapabilirsiniz Arrays.stream(array). Bu arada, isExcludedtestin amacını tamamen anlamakta güçlük çekiyorum . Bir öğesinin EXCLUDE_PATHStam anlamıyla yolun içinde bir yerde bulunup bulunmadığı gerçekten ilginç mi ? Yani isExcluded("my/path/one/foo/bar/baz")geri dönecek trueve isExcluded("foo/bar/baz/my/path/one/")
Holger

3
Harika, Arrays.streamyöntemin farkında değildim, buna işaret ettiğin için teşekkürler. Nitekim, yayınladığım örnek benden başka kimse için oldukça yararsız görünüyor. isExcludedYöntemin davranışının farkındayım , ama bu gerçekten kendim için ihtiyacım olan bir şey, bu yüzden sorunuzu cevaplamak için: evet , kapsama uymayacağı için bahsetmek istemediğim nedenlerden dolayı ilginç orijinal soru.
mcuenez

1
toLowerCaseZaten küçük olan sabite neden uygulanır? Argümana uygulanması gerekmiyor pathmu?
Sebastian Redl

Yanıtlar:


78

Varsayımınız doğru. Akış uygulamanız, for-döngüsünden daha yavaştır.

Bu akış kullanımı for-loop kadar hızlı olmalıdır:

EXCLUDE_PATHS.stream()  
                               .map(String::toLowerCase)
                               .anyMatch(path::contains);

Bu, öğeler arasında yinelenir String::toLowerCase, öğelere tek tek uygulama ve filtre uygular ve eşleşen ilk öğede sonlandırılır .

Her iki collect()ve anyMatch()uç işlemlerdir. anyMatch()ilk bulunan öğeden çıkar, ancak collect()tüm öğelerin işlenmesini gerektirir.


2
Harika, findFirst()kombinasyonunu bilmiyordum filter(). Anlaşılan, do not düşündüm sıra sıra akışları nasıl kullanacağımı biliyorum.
mcuenez

4
Web'de akış API performansı hakkında gerçekten ilginç bazı blog makaleleri ve sunumları var, bunların başlık altında nasıl çalıştığını anlamak için çok yararlı buldum. Bununla ilgileniyorsanız, biraz araştırma yapmanızı kesinlikle tavsiye edebilirim.
Stefan Pries

Düzenlemenizden sonra, diğer cevabın yorumlarında da soruma cevap verdiğiniz için, cevabınızın kabul edilmesi gerektiğini düşünüyorum. Yine de, @ rvit34'e kodu gönderdiği için biraz kredi vermek istiyorum :-)
mcuenez

34

Akışları kullanıp kullanmama kararı, performans değerlendirmesine değil, okunabilirliğe göre verilmelidir. Gerçekten performans söz konusu olduğunda, başka hususlar da vardır.

Senin ile .filter(path::contains).collect(Collectors.toList()).size() > 0yaklaşımda, tüm unsurları işleniyor ve geçici bir içine toplama Listboyutunu karşılaştırarak önce, yine bu hemen hemen hiç iki elementten oluşan bir Akım'da önemli.

Çok .map(String::toLowerCase).anyMatch(path::contains)fazla sayıda öğeniz varsa, kullanmak CPU döngülerini ve hafızayı kurtarabilir. Yine de bu String, bir eşleşme bulunana kadar her birini küçük harf gösterimine dönüştürür . Açıkçası, kullanmanın bir anlamı var

private static final List<String> EXCLUDE_PATHS =
    Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
          .collect(Collectors.toList());

private boolean isExcluded(String path) {
    return EXCLUDE_PATHS.stream().anyMatch(path::contains);
}

yerine. Böylece, her çağrıda küçük harfe dönüşümü tekrarlamanız gerekmez isExcluded. EXCLUDE_PATHSDizelerdeki elemanların sayısı veya uzunlukları gerçekten büyük olursa, kullanmayı düşünebilirsiniz

private static final List<Predicate<String>> EXCLUDE_PATHS =
    Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
          .map(s -> Pattern.compile(s, Pattern.LITERAL).asPredicate())
          .collect(Collectors.toList());

private boolean isExcluded(String path){
    return EXCLUDE_PATHS.stream().anyMatch(p -> p.test(path));
}

Bir dizeyi normal ifade kalıbı olarak derleme LITERAL bayrakla , onun sıradan dizgi işlemleri gibi davranmasını sağlar, ancak motorun, gerçek karşılaştırma söz konusu olduğunda daha verimli olması için örneğin Boyer Moore algoritmasını kullanarak hazırlık için biraz zaman harcamasına izin verir.

Tabii ki, bu yalnızca hazırlıkta harcanan zamanı telafi etmek için yeterli sayıda sonraki test varsa karşılığını verir. Durumun böyle olup olmayacağını belirlemek, bu işlemin performans açısından kritik olup olmayacağının ilk sorusunun yanı sıra, gerçek performans değerlendirmelerinden biridir. Akışların mı yoksafor döngülerin .

Bu arada, yukarıdaki kod örnekleri, bana şüpheli görünen orijinal kodunuzun mantığını koruyor. Kişisel isExcludedyöntem geri döndüğünde true, belirtilen yol listesindeki öğelerin herhangi içeriyorsa, o yüzden dönertrue için /some/prefix/to/my/path/onesıra sıra my/path/one/and/some/suffixhatta /some/prefix/to/my/path/one/and/some/suffix.

Dizge dummy/path/onerousolduğu için bile kriterleri yerine getirdiği kabul edilir ...containsmy/path/one


Olası performans optimizasyonu hakkında güzel bilgiler, teşekkürler. Cevabınızın son kısmıyla ilgili olarak: Yorumunuza verdiğim yanıt tatmin edici değilse, örnek kodumu başkalarının ne istediğimi anlamaları için gerçek bir kod olmaktan çok yardımcı olarak düşünün. Ayrıca, aklınızda daha iyi bir örnek varsa, soruyu her zaman düzenleyebilirsiniz.
mcuenez

3
Yorumunuzu alıyorum, bu operasyon gerçekten istediğiniz şey, bu yüzden onu değiştirmeye gerek yok. Son bölümü gelecekteki okuyucular için saklayacağım, böylece bunun tipik bir işlem olmadığının farkında olacaklar, aynı zamanda daha önce tartışıldığını ve daha fazla yoruma gerek olmadığını da biliyorlar ...
Holger

Aslında akışlar, çalışan bellek miktarı sunucu sınırını
aştığında

21

Evet. Haklısın. Akış yaklaşımınızın bazı ek yükleri olacaktır. Ama böyle bir yapı kullanabilirsiniz:

private boolean isExcluded(String path) {
    return  EXCLUDE_PATHS.stream().map(String::toLowerCase).anyMatch(path::contains);
}

Akışları kullanmanın ana nedeni, kodunuzu daha basit ve okunması kolay hale getirmeleridir.


3
Mi anyMatchiçin bir kısayol filter(...).findFirst().isPresent()?
mcuenez

6
Evet öyle! Bu benim ilk önerimden bile daha iyi.
Stefan Pries

8

Java'daki akışların amacı, paralel kod yazmanın karmaşıklığını basitleştirmektir. Fonksiyonel programlamadan esinlenmiştir. Seri akış sadece kodu daha temiz hale getirmek içindir.

Performans istiyorsak, bunun için tasarlanmış olan parallelStream kullanmalıyız. Seri olan genel olarak daha yavaştır.

İyi hakkında bilgi almak için makale vardır , ve Performans ForLoopStreamParallelStream .

Kodunuzda, ilk eşleşmede aramayı durdurmak için sonlandırma yöntemlerini kullanabiliriz. (anyMatch ...)


5
Küçük akışlar için ve diğer bazı durumlarda, paralel bir akışın başlangıç ​​maliyeti nedeniyle daha yavaş olabileceğini unutmayın. Ve sırasız paralelleştirilebilir bir terminal yerine sıralı bir terminal işleminiz varsa, sonunda yeniden senkronizasyon.
CAD97

0

Başkalarının da birçok iyi noktadan bahsettiği gibi, ancak akış değerlendirmesinde tembel değerlendirmeden bahsetmek istiyorum . Küçük map()harfli yollardan oluşan bir akış oluşturduğumuzda, tüm akışı hemen oluşturmuyoruz, bunun yerine akış tembel olarak inşa ediliyor, bu nedenle performans geleneksel for döngüsüne eşdeğer olmalıdır. Bu tam bir tarama yapmıyor, map()ve anyMatch()aynı zamanda yürütülür. Bir kez anyMatch()true döndüğünde, kısa devre olacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.