Collection.stream (). ForEach () ve Collection.forEach () arasındaki fark nedir?


286

Bununla birlikte .stream(), zincir .filter()akışı gibi paralel işlemler kullanabileceğimi anlıyorum . Ancak küçük işlemleri yürütmem gerekirse (örneğin, listenin öğelerini yazdırma) aralarındaki fark nedir?

collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);

Yanıtlar:


287

Gösterilen örnek gibi basit durumlar için çoğunlukla aynıdır. Bununla birlikte, önemli olabilecek bazı ince farklar vardır.

Bir sorun sipariş vermektir. İle Stream.forEach, sipariş tanımsızdır . Sıralı akışlarla gerçekleşmesi olası değildir, yine de, Stream.forEachrastgele bir sırada yürütmek için spesifikasyon dahilindedir . Bu, paralel akışlarda sıklıkla görülür. Buna karşılık, eğer belirtilirse Iterable.forEach, daima yineleme sırasında yürütülür Iterable.

Başka bir sorun yan etkilerle ilgilidir. Belirtilen işlem Stream.forEachiçin gerekli olan etkileşmeyen . (Bkz. Java.util.stream paket belgesi .) Iterable.forEachPotansiyel olarak daha az kısıtlamaya sahiptir. İçindeki koleksiyonlar için java.util, Iterable.forEachgenellikle Iteratorçoğu hızlı olarak tasarlanmış olan ConcurrentModificationExceptionve koleksiyon yineleme sırasında yapısal olarak değiştirilirse atılacak olan koleksiyonları kullanır. Bununla birlikte, yapısal olmayan modifikasyonlar olan yineleme sırasında izin verdi. Örneğin, ArrayList sınıfı belgeleri "yalnızca bir öğenin değerini ayarlamak yapısal bir değişiklik değildir" der. Böylece,ArrayList.forEachaltta yatan değerleri ArrayListsorunsuz ayarlayabilir .

Eşzamanlı koleksiyonlar yine farklıdır. Başarısızlık yerine, zayıf bir şekilde tutarlı olacak şekilde tasarlanmıştır . Tam tanım bu bağlantıda. Kısaca, düşünün ConcurrentLinkedDeque. Onun geçirilen eylem forEachyöntemine olduğu bile yapısal olarak, altta yatan deque değiştirmesine izin ve ConcurrentModificationExceptionatılan hiçbir zaman. Ancak, meydana gelen değişiklik bu yinelemede görülebilir veya görünmeyebilir. (Dolayısıyla "zayıf" tutarlılık.)

Iterable.forEachSenkronize bir koleksiyon üzerinden yineleniyorsa başka bir fark görülebilir . Böyle bir koleksiyonda, Iterable.forEach koleksiyonun kilidini bir kez alır ve eylem yöntemine yapılan tüm çağrılarda tutar. Stream.forEachÇağrı kilitlemez koleksiyonun spliterator, kullanır ve karışmama hakim kuralına dayandığı. Akışı destekleyen koleksiyon yineleme sırasında değiştirilebilir ve eğer öyleyse, bir ConcurrentModificationExceptionveya tutarsız davranış ortaya çıkabilir.


Iterable.forEach takes the collection's lock. Bu bilgi nereden geliyor? JDK kaynaklarında böyle bir davranış bulamıyorum.
turbanoff


@Stuart, parazit yapmama konusunda ayrıntılı bilgi verebilir misiniz? Stream.forEach () ayrıca ConcurrentModificationException (en azından benim için) atar.
yuranos

1
@ yuranos87 gibi birçok koleksiyon ArrayListeşzamanlı modifikasyon için oldukça sıkı bir kontrole sahiptir ve bu nedenle sık sık fırlar ConcurrentModificationException. Ancak bu özellikle paralel akışlar için garanti edilmez. CME yerine beklenmedik bir cevap alabilirsiniz. Akış kaynağında yapısal olmayan değişiklikleri de düşünün. Paralel akışlar için, hangi iş parçacığının belirli bir öğeyi işleyeceğini veya değiştirildiği sırada işlenip işlenmediğini bilmiyorsunuz. Bu, her koşuda farklı sonuçlar alabileceğiniz ve asla CME elde edemeyeceğiniz bir yarış koşulu oluşturur.
Stuart Marks

30

Bu cevap, döngülerin çeşitli uygulamalarının performansıyla ilgilidir. ÇOK OFTEN (milyonlarca çağrı gibi) olarak adlandırılan döngüler için sadece marjinal olarak alakalı. Çoğu durumda, döngünün içeriği açık ara en pahalı unsur olacaktır. Gerçekten sık sık döngü yaptığınız durumlar için, bu hala ilgi çekici olabilir.

Bu testleri uygulamaya özel olduğundan ( tam kaynak kodu ) hedef sistem altında tekrarlamanız gerekir .

Hızlı bir Linux makinesinde openjdk 1.8.0_111 sürümünü çalıştırıyorum.

integers(10 ^ 0 -> 10 ^ 5 girişler) için değişen boyutlarda bu kodu kullanarak bir Liste üzerinde 10 ^ 6 kez döngüler bir test yazdım .

Sonuçlar aşağıdadır, en hızlı yöntem listedeki girişlerin miktarına bağlı olarak değişir.

Ancak yine de en kötü koşullarda, 10 ^ 5 girişin üzerinde döngü yapmak 10 ^ 6 kez en kötü performans gösteren oyuncu için 100 saniye sürdü, bu nedenle diğer hususlar hemen hemen tüm durumlarda daha önemlidir.

public int outside = 0;

private void forCounter(List<Integer> integers) {
    for(int ii = 0; ii < integers.size(); ii++) {
        Integer next = integers.get(ii);
        outside = next*next;
    }
}

private void forEach(List<Integer> integers) {
    for(Integer next : integers) {
        outside = next * next;
    }
}

private void iteratorForEach(List<Integer> integers) {
    integers.forEach((ii) -> {
        outside = ii*ii;
    });
}
private void iteratorStream(List<Integer> integers) {
    integers.stream().forEach((ii) -> {
        outside = ii*ii;
    });
}

İşte benim zamanlamalarım: milisaniye / işlev / listedeki giriş sayısı. Her çalışma 10 ^ 6 döngüdür.

                           1    10    100    1000    10000
       iterator.forEach   27   116    959    8832    88958
               for:each   53   171   1262   11164   111005
         for with index   39   112    920    8577    89212
iterable.stream.forEach  255   324   1030    8519    88419

Denemeyi tekrarlarsanız, tam kaynak kodunu gönderdim . Lütfen bu yanıtı düzenleyin ve test edilen sistemin gösterimi ile sonuçları ekleyin.


MacBook Pro, 2.5 GHz Intel Core i7, 16 GB, macOS 10.12.6 kullanma:

                           1    10    100    1000    10000
       iterator.forEach   27   106   1047    8516    88044
               for:each   46   143   1182   10548   101925
         for with index   49   145    887    7614    81130
iterable.stream.forEach  393   397   1108    8908    88361

Java 8 Etkin Nokta VM - 3.4GHz Intel Xeon, 8 GB, Windows 10 Pro

                            1    10    100    1000    10000
        iterator.forEach   30   115    928    8384    85911
                for:each   40   125   1166   10804   108006
          for with index   30   120    956    8247    81116
 iterable.stream.forEach  260   237   1020    8401    84883

Java 11 Hotspot VM - 3.4GHz Intel Xeon, 8 GB, Windows 10 Pro
(yukarıdakiyle aynı makine, farklı JDK sürümü)

                            1    10    100    1000    10000
        iterator.forEach   20   104    940    8350    88918
                for:each   50   140    991    8497    89873
          for with index   37   140    945    8646    90402
 iterable.stream.forEach  200   270   1054    8558    87449

Java 11 OpenJ9 VM - 3.4GHz Intel Xeon, 8 GB, Windows 10 Pro
(yukarıdaki ile aynı makine ve JDK sürümü, farklı VM)

                            1    10    100    1000    10000
        iterator.forEach  211   475   3499   33631   336108
                for:each  200   375   2793   27249   272590
          for with index  384   467   2718   26036   261408
 iterable.stream.forEach  515   714   3096   26320   262786

Java 8 Hotspot VM - 2.8GHz AMD, 64 GB, Windows Server 2016

                            1    10    100    1000    10000
        iterator.forEach   95   192   2076   19269   198519
                for:each  157   224   2492   25466   248494
          for with index  140   368   2084   22294   207092
 iterable.stream.forEach  946   687   2206   21697   238457

Java 11 Hotspot VM - 2.8GHz AMD, 64 GB, Windows Server 2016
(yukarıdakiyle aynı makine, farklı JDK sürümü)

                            1    10    100    1000    10000
        iterator.forEach   72   269   1972   23157   229445
                for:each  192   376   2114   24389   233544
          for with index  165   424   2123   20853   220356
 iterable.stream.forEach  921   660   2194   23840   204817

Java 11 OpenJ9 VM - 2.8GHz AMD, 64 GB, Windows Server 2016
(yukarıdaki ile aynı makine ve JDK sürümü, farklı VM)

                            1    10    100    1000    10000
        iterator.forEach  592   914   7232   59062   529497
                for:each  477  1576  14706  129724  1190001
          for with index  893   838   7265   74045   842927
 iterable.stream.forEach 1359  1782  11869  104427   958584

Seçtiğiniz VM uygulaması da Hotspot / OpenJ9 / vb.


3
Bu çok güzel bir cevap, teşekkürler! Ancak ilk bakışta (ve ikincisinden de) hangi yöntemin hangi deneye karşılık geldiği belirsizdir.
torina

Bu cevap kod testi için daha fazla oy gerekiyor gibi hissediyorum :).
Cory

test örnekleri için +1
Centos

8

Bahsettiğiniz ikisi arasında hiçbir fark yoktur, en azından kavramsal olarak, Collection.forEach()sadece bir stenondur.

Dahili olarak stream()sürüm, nesne oluşturma nedeniyle biraz daha fazla ek yüke sahiptir, ancak çalışma süresine bakıldığında orada bir ek yükü yoktur.

Her iki uygulama da collectioniçeriklerin üzerinde bir kez yinelenir ve yineleme sırasında öğeyi yazdırır.


Bahsettiğiniz nesne oluşturma yükü, Streamyaratılmakta olan nesneyi mi yoksa tek tek nesneleri mi kastediyorsunuz ? AFAIK, a Streamöğeleri çoğaltmaz.
Raffi Khatchadourian

30
Bu yanıt, Oracle Corporation'da Java çekirdek kitaplıkları geliştiren beyefendinin yazdığı mükemmel cevapla çelişiyor gibi görünüyor.
Dawood ibn Kareem

0

Collection.forEach (), koleksiyonun yineleyicisini kullanır (belirtilmişse). Bu, öğelerin işleme sırasının tanımlandığı anlamına gelir. Buna karşılık, Collection.stream (). ForEach () işlem sırası tanımlanmamıştır.

Çoğu durumda, bu ikisinden hangisini seçtiğimiz bir fark yaratmaz. Paralel akışlar akışı birden çok iş parçacığında yürütmemize izin verir ve bu gibi durumlarda yürütme sırası tanımlanmamıştır. Java, yalnızca tüm iş parçacıklarının Collectors.toList () gibi herhangi bir terminal işlemi çağrılmadan önce tamamlanmasını gerektirir. İlk olarak forEach () öğesini doğrudan koleksiyonda ve ikincisini paralel bir akışta çağırdığımız bir örneğe bakalım:

list.forEach(System.out::print);
System.out.print(" ");
list.parallelStream().forEach(System.out::print);

Kodu birkaç kez çalıştırırsak, list.forEach () öğesinin ekleme sırasında işlediğini görürken list.parallelStream (). ForEach () her çalıştırmada farklı bir sonuç üretir. Olası bir çıktı:

ABCD CDBA

Bir diğeri:

ABCD DBCA
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.