Jaki jest najbardziej skuteczny sposób na przejrzenie 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();
}
Proszę pamiętać, że to nie jest dokładną kopią tego , tego , tego , czy ten , choć jedna z odpowiedzi na ostatnie pytanie jest blisko. Powodem, dla którego nie jest to duplikat, jest to, że większość z nich porównuje pętle, które wywołujesz get(i)
wewnątrz pętli, zamiast używać iteratora.
Jak sugerowałem na Meta , opublikuję swoją odpowiedź na to pytanie.
java
collections
foreach
Paul Wagland
źródło
źródło
Odpowiedzi:
Jeśli wędrujesz po kolekcji, aby odczytać wszystkie wartości, nie ma różnicy między użyciem iteratora lub nową składnią pętli for, ponieważ nowa składnia używa iteratora pod wodą.
Jeśli jednak rozumiesz przez pętlę starą pętlę w stylu „c”:
Nowa pętla for lub iterator może być znacznie bardziej wydajna, w zależności od podstawowej struktury danych. Powodem tego jest to, że w przypadku niektórych struktur danych
get(i)
jest to operacja O (n), która czyni pętlę operacją O (n 2 ). Tradycyjna lista z linkami jest przykładem takiej struktury danych. Wszystkie iteratory mają jako podstawowy wymóg, żenext()
powinna być operacją O (1), tworząc pętlę O (n).Aby sprawdzić, czy iterator jest używany pod wodą przez nową składnię pętli for, porównaj wygenerowane kody bajtowe z następujących dwóch fragmentów kodu Java. Najpierw pętla for:
Po drugie, iterator:
Jak widać, wygenerowany kod bajtowy jest faktycznie identyczny, więc nie ma negatywnego wpływu na wydajność przy korzystaniu z dowolnej formy. Dlatego powinieneś wybrać formę pętli, która jest dla Ciebie najbardziej atrakcyjna estetycznie, dla większości osób, która będzie pętlą dla każdej, ponieważ ma ona mniej kodu źródłowego.
źródło
for(int i; i < list.size(); i++) {
pętla stylu musi również oceniaćlist.size()
na końcu każdej iteracji, jeśli jest używana, czasem bardziej wydajne jest buforowanie wynikulist.size()
pierwszego.Różnica nie polega na wydajności, ale na możliwościach. Korzystając bezpośrednio z odwołania, masz większą władzę nad jawnym użyciem typu iteratora (np. List.iterator () vs. List.listIterator (), chociaż w większości przypadków zwracają tę samą implementację). Możesz również odwoływać się do iteratora w swojej pętli. Umożliwia to wykonywanie czynności takich jak usuwanie elementów z kolekcji bez uzyskiwania wyjątku ConcurrentModificationException.
na przykład
To jest wporządku:
Nie jest tak, ponieważ spowoduje zgłoszenie wyjątku jednoczesnej modyfikacji:
źródło
Aby rozwinąć własną odpowiedź Paula, wykazał, że kod bajtowy jest taki sam w tym konkretnym kompilatorze (prawdopodobnie javac Sun?), Ale nie gwarantuje się, że różne kompilatory wygenerują ten sam kod bajtowy, prawda? Aby zobaczyć, jaka jest rzeczywista różnica między nimi, przejdźmy do źródła i sprawdź specyfikację języka Java, a konkretnie 14.14.2, „Ulepszone dla instrukcji” :
Innymi słowy, JLS wymaga, aby oba były równoważne. Teoretycznie może to oznaczać marginalne różnice w kodzie bajtowym, ale w rzeczywistości ulepszona pętla for jest wymagana do:
.iterator()
metodę.hasNext()
.next()
Innymi słowy, dla wszystkich praktycznych celów kod bajtowy będzie identyczny lub prawie identyczny. Trudno przewidzieć jakąkolwiek implementację kompilatora, która spowodowałaby znaczącą różnicę między nimi.
źródło
foreach
Pod maską silnika jest stworzenieiterator
, nazywając hasNext () i wywołanie next (), aby uzyskać wartość; Problem z wydajnością pojawia się tylko wtedy, gdy używasz czegoś, co implementuje RandomomAccess.Problemy z wydajnością pętli opartej na iteratorach są następujące:
Iterator<CustomObj> iter = customList.iterator();
);iter.hasNext()
podczas każdej iteracji pętli odbywa się wirtualne wywołanie invokeInterface (przejdź przez wszystkie klasy, a następnie wykonaj skok tablicy metod przed skokiem).hasNext()
wartość wywołania miała wartość: # 1 pobiera bieżącą liczbę i # 2 pobiera całkowitą liczbęiter.next
(więc: przejrzyj wszystkie klasy i wykonaj skok tablicy metod przed skokiem), a także musisz wykonać wyszukiwanie pól: # 1 pobierz indeks, a # 2 odwołanie do tablica do wykonania przesunięcia w nim (w każdej iteracji).Potencjalną optymalizacją jest przejście na
index iteration
wyszukiwanie z pamięcią podręczną:Mamy tutaj:
customList.size()
podczas początkowego tworzenia pętli for w celu uzyskania rozmiarucustomList.get(x)
podczas body for loop, które jest wyszukiwaniem pola do tablicy, a następnie może wykonać przesunięcie do tablicyZmniejszyliśmy tonę wywołań metod, wyszukiwania pól. Nie chcesz tego robić z
LinkedList
czymś, co nie jestRandomAccess
kolekcją obj, w przeciwnym raziecustomList.get(x)
zmieni się w coś, co będzie musiało przejść przezLinkedList
każdą iterację.Jest to idealne rozwiązanie, gdy wiesz, że jest to dowolna
RandomAccess
kolekcja list oparta.źródło
foreach
i tak używa iteratorów pod maską. To naprawdę tylko cukier syntaktyczny.Rozważ następujący program:
Załóżmy, skompilować go
javac Whatever.java
,i odczytać kodu bajtowego z demontażu
main()
, używającjavap -c Whatever
:Widzimy, że
foreach
kompiluje się do programu, który:List.iterator()
Iterator.hasNext()
: wywołujeIterator.next()
i kontynuuje pętlęJeśli chodzi o „dlaczego ta bezużyteczna pętla nie jest zoptymalizowana ze skompilowanego kodu? Widzimy, że nic nie robi z pozycją listy”: cóż, możliwe jest kodowanie iterowalnej
.iterator()
strony z efektami ubocznymi , lub.hasNext()
ma to skutki uboczne lub znaczące konsekwencje.Można łatwo wyobrazić sobie, że iterowalna reprezentacja przewijalnego zapytania z bazy danych może zrobić coś dramatycznego
.hasNext()
( na przykład skontaktowanie się z bazą danych lub zamknięcie kursora, ponieważ osiągnąłeś koniec zestawu wyników).Tak więc, nawet jeśli możemy udowodnić, że nic nie dzieje się w ciele pętli… droższym (trudnym?) Jest udowodnienie, że podczas iteracji nie dzieje się nic znaczącego / wynikowego. Kompilator musi pozostawić to puste ciało w programie.
Najlepsze, na co moglibyśmy oczekiwać, to ostrzeżenie kompilatora . To ciekawe, że
javac -Xlint:all Whatever.java
ma nie ostrzegają nas o tym pustym ciele pętli. IntelliJ IDEA to robi. Wprawdzie skonfigurowałem IntelliJ do korzystania z kompilatora Eclipse, ale to nie może być powód.źródło
Iterator to interfejs w środowisku Java Collections, który zapewnia metody przechodzenia lub iteracji po kolekcji.
Zarówno iterator, jak i pętla for zachowują się podobnie, gdy Twoim motywem jest po prostu przechodzenie przez kolekcję i czytanie jej elementów.
for-each
to tylko jeden ze sposobów na iterację po kolekcji.Na przykład:
I dla każdej pętli można używać tylko na obiektach implementujących interfejs iteratora.
Wróćmy teraz do przypadku pętli for i iteratora.
Różnica pojawia się, gdy próbujesz zmodyfikować kolekcję. W takim przypadku iterator jest bardziej wydajny ze względu na swoją niezawodność . to znaczy. sprawdza iterację zmian w strukturze kolekcji, zanim przejdzie do następnego elementu. Jeśli zostaną znalezione jakieś modyfikacje, zgłosi wyjątek ConcurrentModificationException .
(Uwaga: ta funkcja iteratora ma zastosowanie tylko w przypadku klas kolekcji w pakiecie java.util. Nie ma ona zastosowania do jednoczesnych kolekcji, ponieważ są one z natury bezpieczne)
źródło
Podczas pracy z kolekcjami powinniśmy unikać używania tradycyjnych pętli for. 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 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 dla wykonania. Widzisz różnicę.
źródło