Welches ist der effizienteste Weg, um eine Sammlung zu durchlaufen?
List<Integer> a = new ArrayList<Integer>();
for (Integer integer : a) {
integer.toString();
}
oder
List<Integer> a = new ArrayList<Integer>();
for (Iterator iterator = a.iterator(); iterator.hasNext();) {
Integer integer = (Integer) iterator.next();
integer.toString();
}
Bitte beachten Sie, dass dies kein genaues Duplikat von diesem , diesem , diesem oder jenem ist , obwohl eine der Antworten auf die letzte Frage nahe kommt. Der Grund dafür, dass dies kein Betrug ist, ist, dass die meisten von ihnen Schleifen vergleichen, bei denen Sie get(i)
innerhalb der Schleife aufrufen , anstatt den Iterator zu verwenden.
Wie auf Meta vorgeschlagen, werde ich meine Antwort auf diese Frage veröffentlichen.
java
collections
foreach
Paul Wagland
quelle
quelle
Antworten:
Wenn Sie nur durch die Sammlung wandern, um alle Werte zu lesen, gibt es keinen Unterschied zwischen der Verwendung eines Iterators oder der neuen for-Schleifensyntax, da die neue Syntax nur den Iterator unter Wasser verwendet.
Wenn Sie jedoch mit Schleife die alte "c-style" Schleife meinen:
Dann kann die neue for-Schleife oder der neue Iterator abhängig von der zugrunde liegenden Datenstruktur viel effizienter sein. Der Grund dafür ist, dass es sich bei einigen Datenstrukturen
get(i)
um eine O (n) -Operation handelt, wodurch die Schleife zu einer O (n 2 ) -Operation wird. Eine herkömmliche verknüpfte Liste ist ein Beispiel für eine solche Datenstruktur. Alle Iteratoren haben als Grundanforderungnext()
eine O (1) -Operation, wodurch die Schleife O (n) wird.Vergleichen Sie die generierten Bytecodes aus den folgenden beiden Java-Snippets, um zu überprüfen, ob der Iterator von der neuen for-Schleifensyntax unter Wasser verwendet wird. Zuerst die for-Schleife:
Und zweitens der Iterator:
Wie Sie sehen können, ist der generierte Bytecode praktisch identisch, sodass die Verwendung beider Formulare keine Leistungseinbußen mit sich bringt. Daher sollten Sie die Form der Schleife wählen, die für Sie am ästhetischsten ist, für die meisten Personen, die für jede Schleife die richtige sind, da diese weniger Code für das Boilerplate enthält.
quelle
for(int i; i < list.size(); i++) {
Stilschleife muss auchlist.size()
am Ende jeder Iteration ausgewertet werden. Wenn sie verwendet wird, ist es manchmal effizienter, das Ergebnis vonlist.size()
first zwischenzuspeichern.Der Unterschied liegt nicht in der Leistung, sondern in der Leistungsfähigkeit. Wenn Sie eine Referenz direkt verwenden, haben Sie mehr Macht über die explizite Verwendung eines Iteratortyps (z. B. List.iterator () vs. List.listIterator (), obwohl in den meisten Fällen dieselbe Implementierung zurückgegeben wird). Sie haben auch die Möglichkeit, auf den Iterator in Ihrer Schleife zu verweisen. Auf diese Weise können Sie beispielsweise Elemente aus Ihrer Sammlung entfernen, ohne eine ConcurrentModificationException zu erhalten.
z.B
Das ist in Ordnung:
Dies ist nicht der Fall, da eine gleichzeitige Änderungsausnahme ausgelöst wird:
quelle
Um Pauls eigene Antwort zu erweitern, hat er gezeigt, dass der Bytecode auf diesem bestimmten Compiler (vermutlich Suns Javac?) Der gleiche ist, aber es ist nicht garantiert , dass verschiedene Compiler denselben Bytecode generieren, oder? Um zu sehen, was der tatsächliche Unterschied zwischen den beiden ist, gehen wir direkt zur Quelle und überprüfen die Java-Sprachspezifikation, insbesondere 14.14.2, "Die erweiterte for-Anweisung" :
Mit anderen Worten, das JLS verlangt, dass beide gleichwertig sind. Theoretisch könnte dies marginale Unterschiede im Bytecode bedeuten, aber in Wirklichkeit ist die erweiterte for-Schleife erforderlich, um:
.iterator()
Methode auf.hasNext()
.next()
Mit anderen Worten, für alle praktischen Zwecke ist der Bytecode identisch oder nahezu identisch. Es ist schwer, sich eine Compiler-Implementierung vorzustellen, die zu einem signifikanten Unterschied zwischen den beiden führen würde.
quelle
Die
foreach
Unterwelt erstellt dasiterator
, ruft hasNext () auf und ruft next () auf, um den Wert zu erhalten. Das Problem mit der Leistung tritt nur auf, wenn Sie etwas verwenden, das den RandomomAccess implementiert.Leistungsprobleme mit der iteratorbasierten Schleife sind folgende:
Iterator<CustomObj> iter = customList.iterator();
);iter.hasNext()
Während jeder Iteration der Schleife gibt es einen virtuellen Aufruf von invokeInterface (gehen Sie alle Klassen durch und suchen Sie dann vor dem Sprung nach Methodentabellen).hasNext()
die Aufrufzahl den Wert erhält: # 1 erhält die aktuelle Anzahl und # 2 erhält die Gesamtanzahliter.next
(also: Alle Klassen durchgehen und vor dem Sprung nach Methoden suchen). Außerdem muss nach Feldern gesucht werden: # 1 erhält den Index und # 2 erhält den Verweis auf die Array, um den Offset zu machen (in jeder Iteration).Eine mögliche Optimierung besteht darin, zu einer
index iteration
Suche mit zwischengespeicherter Größe zu wechseln :Hier haben wir:
customList.size()
beim ersten Erstellen der for-Schleife, um die Größe zu ermittelncustomList.get(x)
während der body for-Schleife ist eine Feldsuche für das Array und kann dann den Offset in das Array ausführenWir haben eine Menge Methodenaufrufe und Feldsuchen reduziert. Dies möchten Sie nicht mit
LinkedList
oder mit etwas tun , das keinRandomAccess
Sammlungsobjekt ist, sonstcustomList.get(x)
wird das zu etwas, das dasLinkedList
bei jeder Iteration durchlaufen muss.Dies ist perfekt, wenn Sie wissen, dass es sich um eine beliebige
RandomAccess
Listensammlung handelt.quelle
foreach
verwendet sowieso Iteratoren unter der Haube. Es ist wirklich nur syntaktischer Zucker.Betrachten Sie das folgende Programm:
Lassen Sie uns kompilieren es mit
javac Whatever.java
,und lesen Sie den zerlegten Bytecode
main()
, mitjavap -c Whatever
:Wir können sehen, dass
foreach
sich das zu einem Programm zusammensetzt, das:List.iterator()
Iterator.hasNext()
: ruftIterator.next()
die Schleife auf und setzt sie fortWas "Warum wird diese nutzlose Schleife nicht aus dem kompilierten Code heraus optimiert? Wir können sehen, dass sie nichts mit dem Listenelement zu tun hat": Nun, es ist Ihnen möglich, Ihre iterierbare Schleife so zu codieren, dass sie
.iterator()
Nebenwirkungen hat , oder so, das.hasNext()
hat Nebenwirkungen oder bedeutsame Konsequenzen.Sie können sich leicht vorstellen, dass eine iterable Darstellung einer scrollbaren Abfrage aus einer Datenbank dramatische
.hasNext()
Auswirkungen hat (z. B. Kontaktaufnahme mit der Datenbank oder Schließen eines Cursors, weil Sie das Ende der Ergebnismenge erreicht haben).Obwohl wir also beweisen können, dass im Schleifenkörper nichts passiert, ist es teurer (unlösbar?) Zu beweisen, dass beim Iterieren nichts Sinnvolles / Konsequenzielles passiert. Der Compiler muss diesen leeren Schleifenkörper im Programm belassen.
Das Beste, auf das wir hoffen können, ist eine Compiler- Warnung . Es ist interessant, dass
javac -Xlint:all Whatever.java
uns nicht vor diesem leeren Schleifenkörper gewarnt wird. IntelliJ IDEA tut dies jedoch. Zugegeben, ich habe IntelliJ für die Verwendung von Eclipse Compiler konfiguriert, aber das ist möglicherweise nicht der Grund dafür.quelle
Iterator ist eine Schnittstelle im Java Collections-Framework, die Methoden zum Durchlaufen oder Durchlaufen einer Sammlung bereitstellt.
Sowohl der Iterator als auch die for-Schleife verhalten sich ähnlich, wenn Sie lediglich eine Sammlung durchlaufen möchten, um ihre Elemente zu lesen.
for-each
ist nur eine Möglichkeit, die Sammlung zu durchlaufen.Beispielsweise:
Und for-each-Schleife kann nur für Objekte verwendet werden, die die Iteratorschnittstelle implementieren.
Nun zurück zum Fall von for-Schleife und Iterator.
Der Unterschied entsteht, wenn Sie versuchen, eine Sammlung zu ändern. In diesem Fall ist der Iterator aufgrund seiner Fail-Fast-Eigenschaft effizienter . dh. Es prüft, ob sich die Struktur der zugrunde liegenden Sammlung geändert hat, bevor das nächste Element durchlaufen wird. Wenn Änderungen gefunden werden, wird die ConcurrentModificationException ausgelöst .
(Hinweis: Diese Funktionalität des Iterators gilt nur für Sammlungsklassen im Paket java.util. Sie gilt nicht für gleichzeitige Sammlungen, da sie von Natur aus ausfallsicher sind.)
quelle
Wir sollten vermeiden, traditionelle for-Schleifen zu verwenden, wenn wir mit Sammlungen arbeiten. Der einfache Grund, den ich geben werde, ist, dass die Komplexität der for-Schleife in der Größenordnung von O (sqr (n)) liegt und die Komplexität des Iterators oder sogar der erweiterten for-Schleife nur O (n) beträgt. Es ergibt sich also ein Leistungsunterschied. Nehmen Sie einfach eine Liste mit etwa 1000 Elementen und drucken Sie sie auf beide Arten aus. und drucken Sie auch den Zeitunterschied für die Ausführung. Sie können den Unterschied sehen.
quelle