Która jest bardziej wydajna, pętla for-each, czy iterator?

Jaki jest najskuteczniejszy sposób na przemierzenie kolekcji?

List<Integer>  a = new ArrayList<Integer>();
for (Integer integer : a) {
  integer.toString();
}

Lub

List<Integer>  a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();) {
   Integer integer = (Integer) iterator.next();
   integer.toString();
}

Należy pamiętać, że nie jest to dokładny duplikat tego, to, to, lub to , chociaż jedna z odpowiedzi na ostatnie pytanie jest bliska. Powodem, dla którego nie jest to oszustwo, jest to, że większość z nich porównuje pętle, w których wywołujesz get(i) wewnątrz pętli, zamiast używać iteratora.

Zgodnie z sugestią na Meta I będę zamieszczał moją odpowiedź na to pytanie.

Author: Community, 2010-01-21

7 answers

Jeśli po prostu wędrujesz po kolekcji, aby odczytać wszystkie wartości, to nie ma różnicy między używaniem iteratora lub składni pętli new for, ponieważ nowa składnia po prostu używa iteratora pod wodą.

Jeśli jednak masz na myśli pętlę starej pętli "c-style":

for(int i=0; i<list.size(); i++) {
   Object o = list.get(i);
}

Wtedy nowa pętla for, lub iterator, może być o wiele bardziej wydajna, w zależności od podstawowej struktury danych. Powodem tego jest to, że dla niektórych struktur danych get(i) jest operacją O(n), która sprawia, że pętla jest O (n2) operacja. Tradycyjna lista połączona jest przykładem takiej struktury danych. Wszystkie Iteratory mają jako podstawowy wymóg, że next() powinna być operacją O(1), tworząc pętlę O (n).

Aby sprawdzić, czy iterator jest używany pod wodą przez składnię pętli new for, porównaj wygenerowane bajtowe kody z następujących dwóch fragmentów Javy. Pierwsza pętla for:

List<Integer>  a = new ArrayList<Integer>();
for (Integer integer : a)
{
  integer.toString();
}
// Byte code
 ALOAD 1
 INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;
 ASTORE 3
 GOTO L2
L3
 ALOAD 3
 INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object;
 CHECKCAST java/lang/Integer
 ASTORE 2 
 ALOAD 2
 INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String;
 POP
L2
 ALOAD 3
 INVOKEINTERFACE java/util/Iterator.hasNext()Z
 IFNE L3

A po drugie iterator:

List<Integer>  a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();)
{
  Integer integer = (Integer) iterator.next();
  integer.toString();
}
// Bytecode:
 ALOAD 1
 INVOKEINTERFACE java/util/List.iterator()Ljava/util/Iterator;
 ASTORE 2
 GOTO L7
L8
 ALOAD 2
 INVOKEINTERFACE java/util/Iterator.next()Ljava/lang/Object;
 CHECKCAST java/lang/Integer
 ASTORE 3
 ALOAD 3
 INVOKEVIRTUAL java/lang/Integer.toString()Ljava/lang/String;
 POP
L7
 ALOAD 2
 INVOKEINTERFACE java/util/Iterator.hasNext()Z
 IFNE L8

Jak widać, wygenerowany bajt kod jest w rzeczywistości identyczny, więc nie ma kary za wykonanie żadnej z form. Dlatego powinieneś wybrać formę pętli, która jest dla Ciebie najbardziej estetyczna, dla większości osób będzie to pętla for-each, ponieważ ma mniej kodu boilerplate.

 269
Author: Paul Wagland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-09 20:04:38

Różnica nie polega na wydajności, ale na możliwościach. Podczas używania referencji bezpośrednio masz większą władzę nad jawnym używaniem typu iteratora (np. List.iterator () vs. List.listIterator (), chociaż w większości przypadków zwracają tę samą implementację). Masz również możliwość odwoływania się do iteratora w swojej pętli. Pozwala to na usuwanie elementów z kolekcji bez uzyskiwania ConcurrentModificationException.

Np.

To jest ok:

Set<Object> set = new HashSet<Object>();
// add some items to the set

Iterator<Object> setIterator = set.iterator();
while(setIterator.hasNext()){
     Object o = setIterator.next();
     if(o meets some condition){
          setIterator.remove();
     }
}

To nie jest, ponieważ rzuci równoległy wyjątek modyfikacji:

Set<Object> set = new HashSet<Object>();
// add some items to the set

for(Object o : set){
     if(o meets some condition){
          set.remove(o);
     }
}
 106
Author: Michael Krauklis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 22:04:10

Aby rozwinąć własną odpowiedź Paula, zademonstrował, że kod bajtowy jest taki sam na tym konkretnym kompilatorze (przypuszczalnie javac Sun ' a?) ale różne Kompilatory nie są gwarantowane do generowania tego samego bajtowego kodu, prawda? Aby zobaczyć, jaka jest rzeczywista różnica między tymi dwoma, przejdźmy od razu do źródła i sprawdźmy specyfikację języka Java, a konkretnie 14.14.2, "the enhanced for statement":

Rozszerzone for twierdzenie jest równoważne podstawowa for wypowiedź formularza:

for (I #i = Expression.iterator(); #i.hasNext(); ) {
    VariableModifiers(opt) Type Identifier = #i.next();    
    Statement 
}

Innymi słowy, JLS wymaga, aby oba te elementy były równoważne. W teorii może to oznaczać marginalne różnice w kodzie bajtowym, ale w rzeczywistości pętla enhanced for jest wymagana do:

  • wywołanie metody .iterator()
  • użycie .hasNext()
  • Udostępnij zmienną lokalną za pomocą .next()

Innymi słowy, dla wszystkich praktycznych celów kod bajtowy będzie identyczny lub prawie identyczny. To trudne. przewidywanie dowolnej implementacji kompilatora, która spowodowałaby jakąkolwiek znaczącą różnicę między tymi dwoma.

 23
Author: Cowan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-22 04:17:15

foreach underhood tworzy iterator, wywołanie hasNext () i wywołanie next () w celu uzyskania wartości; problem z wydajnością występuje tylko wtedy, gdy używasz czegoś, co implementuje RandomomAccess.

for (Iterator<CustomObj> iter = customList.iterator(); iter.hasNext()){
   CustomObj custObj = iter.next();
   ....
}

Problemy z wydajnością pętli opartej na iteratorze są spowodowane tym, że:

  1. przydzielanie obiektu nawet jeśli lista jest pusta (Iterator<CustomObj> iter = customList.iterator(););
  2. iter.hasNext() podczas każdej iteracji pętli odbywa się wirtualne wywołanie invokeInterface (przejść przez wszystkie klas, a następnie przeszukiwanie tabeli metod przed skokiem).
  3. implementacja iteratora musi przeszukiwać co najmniej 2 pola, aby hasNext() wywołanie było wartością: #1 get current count and #2 get total count
  4. wewnątrz pętli ciała znajduje się kolejne wirtualne wywołanie invokeInterface iter.next (więc: przejrzyj wszystkie klasy i przeszukaj tabelę metod przed skokiem), a także przeszukaj pola: #1 Uzyskaj indeks i # 2 uzyskaj odniesienie do tablicy, aby wykonać przesunięcie do niego (w każdej iteracji).

Potencjalną optymalizacją jest przejście na index iteration przy wyszukiwaniu rozmiaru pamięci podręcznej:

for(int x = 0, size = customList.size(); x < size; x++){
  CustomObj custObj = customList.get(x);
  ...
}

Tutaj mamy:

  1. jedno wywołanie wirtualnej metody invokeInterface customList.size() przy początkowym utworzeniu pętli for, aby uzyskać rozmiar
  2. wywołanie metody get customList.get(x) podczas pętli body for, która jest wyszukiwaniem pól do tablicy, a następnie może wykonać przesunięcie do tablicy

Zmniejszyliśmy ton metody rozmowy, poszukiwania w terenie. Nie chcesz tego robić z LinkedList lub z czymś, co nie jest RandomAccess zbiorem obj, w przeciwnym razie customList.get(x) zmieni się w coś, co musi przejść LinkedList na każdej iteracji.

Jest to idealne rozwiązanie, gdy wiesz, że jest to dowolny {12]} zbiór bazujący na liście.

 4
Author: denis_lor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-17 09:45:09

foreach i tak używa iteratorów pod maską. To naprawdę jest tylko składniowy cukier.

Rozważ następujący program:

import java.util.List;
import java.util.ArrayList;

public class Whatever {
    private final List<Integer> list = new ArrayList<>();
    public void main() {
        for(Integer i : list) {
        }
    }
}

Skompilujmy to za pomocą javac Whatever.java,
I odczytać dezasemblowany kod bajtowy main(), używając javap -c Whatever:

public void main();
  Code:
     0: aload_0
     1: getfield      #4                  // Field list:Ljava/util/List;
     4: invokeinterface #5,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
     9: astore_1
    10: aload_1
    11: invokeinterface #6,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
    16: ifeq          32
    19: aload_1
    20: invokeinterface #7,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
    25: checkcast     #8                  // class java/lang/Integer
    28: astore_2
    29: goto          10
    32: return

Widzimy, że foreach kompiluje się do programu, który:

  • tworzy iterator używając List.iterator()
  • If Iterator.hasNext(): wywołuje Iterator.next() i kontynuuje pętlę

Co do "dlaczego ta bezużyteczna pętla nie dostaje zoptymalizowany z skompilowanego kodu? widzimy, że nie robi to nic z elementem listy": cóż, można kodować iterable tak, że .iterator() ma skutki uboczne, lub tak, że .hasNext() ma skutki uboczne lub znaczące konsekwencje.

Możesz łatwo sobie wyobrazić, że iterable reprezentujące przewijalne zapytanie z bazy danych może zrobić coś dramatycznego na .hasNext() (Jak kontakt z bazą danych lub zamknięcie kursora, ponieważ osiągnąłeś koniec wyniku set).

Więc, mimo że możemy udowodnić, że nic nie dzieje się w ciele pętli ... to jest droższe (trudniejsze?), aby udowodnić, że nic sensownego/wynikowego nie dzieje się, gdy iteracja. Kompilator musi pozostawić w programie pustą treść pętli.

Najlepszym, na jaki mogliśmy liczyć, będzie kompilator warning . To ciekawe, że javac -Xlint:all Whatever.java czy Nie ostrzega nas o tym pustym ciele pętli. IntelliJ IDEA jednak. Co prawda skonfigurowałem IntelliJ do używania Kompilator Eclipse, ale to może nie być powodem.

Tutaj wpisz opis obrazka

 3
Author: Birchlabs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-07 12:14:22

Iterator jest interfejsem w Java Collections framework, który zapewnia metody do przechodzenia lub iteracji nad kolekcją.

Zarówno iterator, jak i pętla for działają podobnie, gdy twoim motywem jest po prostu przemierzanie zbioru, aby odczytać jego elementy.

for-each jest tylko jednym ze sposobów na iterację kolekcji.

Na przykład:

List<String> messages= new ArrayList<>();

//using for-each loop
for(String msg: messages){
    System.out.println(msg);
}

//using iterator 
Iterator<String> it = messages.iterator();
while(it.hasNext()){
    String msg = it.next();
    System.out.println(msg);
}

I for-each pętla może być używana tylko na obiektach implementujących interfejs iteratora.

Teraz wróćmy do przypadku pętli for I iterator.

Różnica pojawia się, gdy próbujesz zmodyfikować kolekcję. W tym przypadku iterator jest bardziej wydajny ze względu na jego właściwość Fail-fast. ie. sprawdza ona wszelkie zmiany w strukturze bazowego zbioru przed iteracją nad następnym elementem. Jeśli zostaną znalezione jakieś modyfikacje, wyrzuci ConcurrentModificationException .

(Uwaga: ta funkcjonalność iteratora ma zastosowanie tylko w przypadku klas kolekcji w java.pakiet util. Nie ma zastosowania do zbiorów równoległych, ponieważ są one z natury bezpieczne)

 1
Author: eccentricCoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-01 16:31:13

Powinniśmy unikać używania tradycyjnej pętli for podczas pracy ze zbiorami. Prostym powodem, który podam, jest to, że złożoność pętli for jest rzędu O(sqr(n)), A złożoność iteratora lub nawet rozszerzonej pętli for jest po prostu O (n). Daje to więc różnicę w wydajności.. Wystarczy wziąć listę około 1000 pozycji i wydrukować ją w obie strony. a także wydrukować różnicę czasu wykonania. Widać różnicę.

 -8
Author: Chandan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-28 12:10:27