Wenn ich ein Objekt habe, das die Map
Schnittstelle in Java implementiert, und jedes darin enthaltene Paar durchlaufen möchte, wie gehe ich dann am effizientesten durch die Karte?
Hängt die Reihenfolge der Elemente von der spezifischen Kartenimplementierung ab, die ich für die Schnittstelle habe?
java
dictionary
collections
iteration
iMack
quelle
quelle
Antworten:
quelle
remove
Methode aufrufen müssen . Wenn dies der Fall ist, zeigt Ihnen diese andere Antwort , wie es geht. Andernfalls ist die erweiterte Schleife, wie in der obigen Antwort gezeigt, der richtige Weg.map.values()
oder verwendenmap.keySet()
möchten.Um die anderen Antworten zusammenzufassen und mit dem zu kombinieren, was ich weiß, habe ich 10 Hauptmethoden gefunden, um dies zu tun (siehe unten). Außerdem habe ich einige Leistungstests geschrieben (siehe Ergebnisse unten). Wenn wir beispielsweise die Summe aller Schlüssel und Werte einer Karte ermitteln möchten, können wir schreiben:
Verwenden von Iterator und Map.Entry
Verwenden von foreach und Map.Entry
Verwenden von forEach aus Java 8
Verwenden von keySet und foreach
Verwenden von keySet und Iterator
Verwenden von for und Map.Entry
Verwenden der Java 8 Stream-API
Parallele Verwendung der Java 8 Stream-API
Verwenden von IterableMap von
Apache Collections
Verwenden von MutableMap of Eclipse (CS) -Sammlungen
Leistungstests (Modus = AverageTime, System = Windows 8.1 64-Bit, Intel i7-4790 3,60 GHz, 16 GB)
Für eine kleine Karte (100 Elemente) ist die Punktzahl 0,308 die beste
Für eine Karte mit 10000 Elementen ist die Punktzahl 37.606 die beste
Für eine Karte mit 100000 Elementen ist die Punktzahl 1184.767 die beste
Diagramme (Leistungstests abhängig von der Kartengröße)
Tabelle (Leistungstests abhängig von der Kartengröße)
Alle Tests sind auf GitHub .
quelle
long sum = 0; map.forEach( /* accumulate in variable sum*/);
dessum
Long, was möglicherweise langsamer ist alsstream.mapToInt(/*whatever*/).sum
beispielsweise. Natürlich können Sie das Erfassen des Status nicht immer vermeiden, aber dies kann eine vernünftige Ergänzung sein auf die Bank.AtomicInteger
, um das Problem zu lösen.x±e
impliziert, dass es Ergebnisse innerhalb des Intervalls vonx-e
bis gabx+e
, sodass das schnellste Ergebnis (1184.767±332.968
) von852
bis reicht1518
, während das zweitlangsamste (1706.676±436.867
) zwischen1270
und liegt2144
, sodass sich die Ergebnisse immer noch erheblich überlappen. Schauen Sie sich nun das langsamste Ergebnis an3289.866±1445.564
, was bedeutet , dass zwischen1844
und abweichen,4735
und Sie wissen, dass diese Testergebnisse bedeutungslos sind.while
vs. einerfor
Schleife ist keine andere Technik zum Iterieren. Und ich bin überrascht, dass sie in Ihren Tests solche Unterschiede aufweisen - was darauf hindeutet, dass die Tests nicht ordnungsgemäß von externen Faktoren isoliert sind, die nicht mit den Dingen zusammenhängen, die Sie testen möchten.In Java 8 können Sie dies mit den neuen Lambdas-Funktionen sauber und schnell tun:
Der Typ von
k
undv
wird vom Compiler abgeleitet und muss nichtMap.Entry
mehr verwendet werden.Kinderleicht!
quelle
map.entrySet().stream()
docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.htmlJa, die Reihenfolge hängt von der spezifischen Map-Implementierung ab.
@ ScArcher2 hat die elegantere Java 1.5-Syntax . In 1.4 würde ich so etwas machen:
quelle
for
Konstrukt asfor (Entry e : myMap.entrySet)
die Sammlung nicht ändern können, aber das Beispiel, wie @HanuAthena erwähnt hat, sollte funktionieren, da es Ihnen denIterator
Umfang gibt. (Es sei denn, ich vermisse etwas ...)Entry thisEntry = (Entry) entries.next();
: erkennt nichtEntry
. Ist das Pseudocode für etwas anderes?java.util.Map.Entry
.Typischer Code zum Durchlaufen einer Karte ist:
HashMap
ist die kanonische Map-Implementierung und gibt keine Garantien ab (oder sollte die Reihenfolge nicht ändern, wenn keine Mutationsoperation daran ausgeführt wird).SortedMap
gibt Einträge zurück, die auf der natürlichen Reihenfolge der Schlüssel basieren, oder aComparator
, falls angegeben.LinkedHashMap
Gibt entweder Einträge in Einfügereihenfolge oder Zugriffsreihenfolge zurück, je nachdem, wie sie erstellt wurden.EnumMap
Gibt Einträge in natürlicher Reihenfolge der Schlüssel zurück.(Update: Ich denke, das stimmt nicht mehr. ) Beachten Sie, dass der
IdentityHashMap
entrySet
Iterator derzeit eine eigenartige Implementierung hat, dieMap.Entry
für jedes Element imentrySet
! Die gleiche Instanz zurückgibt. Jedes Mal, wenn ein neuer Iterator voranschreitet,Map.Entry
wird der aktualisiert.quelle
LinkedHashMap
Zählung. Diejenigen , durchiterator
,spliterator
,entrySet
etc., ändern nicht den Auftrag.Beispiel für die Verwendung von Iterator und Generika:
quelle
Iterator
eine for-Schleife einfügen, um den Umfang zu begrenzen.for (Iterator<Map.Entry<K, V>> entries = myMap.entrySet().iterator(); entries.hasNext(); ) { Map.Entry<K, V> entry = entries.next(); }
. Mit diesem Konstrukt beschränken wir den Umfang (Sichtbarkeit der Variablen)entries
auf die for-Schleife.Dies ist eine zweiteilige Frage:
Wie man über die Einträge einer Karte iteriert - @ ScArcher2 hat das perfekt beantwortet .
Was ist die Reihenfolge der Iteration? Wenn Sie nur verwenden
Map
, gibt es streng genommen keine Bestellgarantien . Sie sollten sich also nicht wirklich auf die Reihenfolge verlassen, die von einer Implementierung vorgegeben wird. DieSortedMap
Schnittstelle wird jedoch erweitertMap
und bietet genau das, wonach Sie suchen. Implementierungen ergeben immer eine konsistente Sortierreihenfolge.NavigableMap
ist eine weitere nützliche Erweiterung - dies ist eineSortedMap
mit zusätzlichen Methoden zum Suchen von Einträgen anhand ihrer geordneten Position im Schlüsselsatz. So möglicherweise kann dies die Notwendigkeit entfernen in erster Linie für Iterieren - Sie könnten der Lage sein , die spezifisch findenentry
Sie nach der Verwendung die sindhigherEntry
,lowerEntry
,ceilingEntry
, oderfloorEntry
Methoden. DiedescendingMap
Methode bietet Ihnen sogar eine explizite Methode zum Umkehren der Durchlaufreihenfolge .quelle
Es gibt verschiedene Möglichkeiten, die Karte zu durchlaufen.
Hier ist ein Vergleich ihrer Leistung für einen gemeinsamen Datensatz, der in der Karte gespeichert ist, indem eine Million Schlüsselwertpaare in der Karte gespeichert werden und über die Karte iteriert werden.
1) Verwenden Sie
entrySet()
in für jede Schleife50 Millisekunden
2) Verwenden Sie
keySet()
in für jede Schleife76 Millisekunden
3) Verwenden
entrySet()
und Iterator50 Millisekunden
4) Verwenden
keySet()
und Iterator75 Millisekunden
Ich habe verwiesen
this link
.quelle
Der richtige Weg, dies zu tun, besteht darin, die akzeptierte Antwort zu verwenden, da sie am effizientesten ist. Ich finde, der folgende Code sieht etwas sauberer aus.
quelle
O(1) = 2*O(1)
ist so ziemlich die Definition der großen O-Notation. Sie haben Recht damit, dass es etwas langsamer läuft, aber in Bezug auf die Komplexität sind sie gleich.2
über eine, wie IterierenentrySet()
überhaupt eine Lookup nicht tragen; Es ist nur eine lineare Durchquerung aller Einträge. Im Gegensatz dazu ist das Durchlaufen derkeySet()
Suche und das Durchführen einer Suche pro Schlüssel mit einer Suche pro Schlüssel verbunden. Wir sprechen hier also von null Suchvorgängen im Vergleich zu n Suchvorgängen, wobei n die Größe des Schlüssels istMap
. Der Faktor ist also weit darüber hinaus2
…Zu Ihrer Information, Sie können auch verwenden
map.keySet()
undmap.values()
wenn Sie nur an Schlüsseln / Werten der Karte interessiert sind und nicht an den anderen.quelle
Mit Java 8 können Sie Map mit forEach- und Lambda-Ausdrücken iterieren.
quelle
Bei Eclipse-Sammlungen würden Sie die
forEachKeyValue
Methode für dieMapIterable
Schnittstelle verwenden, die von denMutableMap
undImmutableMap
-Schnittstellen und ihren Implementierungen geerbt wird .Mit einer anonymen inneren Klasse können Sie den Code wie folgt schreiben:
Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.
quelle
In Java 1.8 (Java 8) ist dies durch die Verwendung der forEach- Methode aus Aggregatoperationen ( Stream-Operationen ), die Iteratoren aus der Iterable Interface ähnelt, viel einfacher geworden .
Kopieren Sie einfach die folgende Anweisung in Ihren Code und benennen Sie die HashMap- Variable von hm in Ihre HashMap-Variable um, um das Schlüssel-Wert-Paar auszudrucken.
Unten ist der Beispielcode, den ich mit Lambda Expression ausprobiert habe . Dieses Zeug ist so cool. Müssen versuchen.
Auch kann man Spliterator für das gleiche verwenden.
AKTUALISIEREN
Einschließlich Dokumentationslinks zu Oracle Docs. Weitere Informationen zu Lambda finden Sie unter diesem Link. Lesen Sie Aggregate Operations. Für Spliterator klicken Sie auf diesen Link .
quelle
Theoretisch hängt der effizienteste Weg von der Implementierung von Map ab. Der offizielle Weg, dies zu tun, ist ein Aufruf
map.entrySet()
, der eine Menge von zurückgibtMap.Entry
, von denen jede einen Schlüssel und einen Wert (entry.getKey()
undentry.getValue()
) enthält.In einer eigenwilligen Umsetzung könnte es einen Unterschied machen , ob Sie verwenden
map.keySet()
,map.entrySet()
oder etwas anderes. Aber ich kann mir keinen Grund vorstellen, warum jemand es so schreiben würde. Höchstwahrscheinlich spielt es für die Leistung keine Rolle, was Sie tun.Und ja, die Reihenfolge hängt von der Implementierung ab - sowie (möglicherweise) von der Reihenfolge der Einfügung und anderen schwer zu kontrollierenden Faktoren.
[edit] Ich habe
valueSet()
ursprünglich geschrieben, aber natürlichentrySet()
ist das eigentlich die Antwort.quelle
Java 8
Wir haben eine
forEach
Methode, die einen Lambda-Ausdruck akzeptiert . Wir haben auch Stream- APIs. Betrachten Sie eine Karte:Über Tasten iterieren:
Über Werte iterieren:
Durch Einträge iterieren (Verwenden von forEach und Streams):
Der Vorteil von Streams ist, dass sie einfach parallelisiert werden können, falls wir dies möchten. Wir müssen einfach
parallelStream()
anstelle vonstream()
oben verwenden.forEachOrdered
vsforEach
mit Streams? DasforEach
folgt nicht der Begegnungsreihenfolge (falls definiert) und ist von Natur aus nicht deterministisch, wo wie dasforEachOrdered
tut. DiesforEach
garantiert nicht, dass die Bestellung beibehalten wird. Überprüfen Sie dies auch für mehr.quelle
Java 8:
Sie können Lambda-Ausdrücke verwenden:
Weitere Informationen finden Sie hier .
quelle
myMap.forEach( (currentKey,currentValue) -> /* action */ );
ist viel prägnanter.Versuchen Sie dies mit Java 1.4:
quelle
In Map kann man über Iteration
keys
und / odervalues
und / oderboth (e.g., entrySet)
abhängig von seinem Interesse an_ Like:Durch
keys -> keySet()
die Karte iterieren :Durch
values -> values()
die Karte iterieren :Durch
both -> entrySet()
die Karte iterieren :Darüber hinaus gibt es drei verschiedene Möglichkeiten, um durch eine HashMap zu iterieren. Sie sind wie folgt__
quelle
Am kompaktesten mit Java 8:
quelle
Wenn Sie eine generische untypisierte Karte haben, können Sie Folgendes verwenden:
quelle
ODER
quelle
Wenn die Effizienz des Schleifens der Schlüssel für Ihre App Priorität hat, wählen Sie eine
Map
Implementierung, die die Schlüssel in der gewünschten Reihenfolge verwaltet.Ja absolut.
Map
Implementierungen versprechen eine bestimmte Iterationsreihenfolge, andere nicht.Map
behalten unterschiedliche Reihenfolge der Schlüssel-Wert-Paare bei.In dieser von mir erstellten Tabelle sind die verschiedenen
Map
mit Java 11 gebündelten Implementierungen zusammengefasst. Beachten Sie insbesondere die Spalte für die Iterationsreihenfolge . Klicken / tippen Sie zum Zoomen.Sie können sehen, dass es vier
Map
Implementierungen gibt, die eine Reihenfolge aufrechterhalten :TreeMap
ConcurrentSkipListMap
LinkedHashMap
EnumMap
NavigableMap
SchnittstelleZwei davon implementieren die
NavigableMap
Schnittstelle:TreeMap
&ConcurrentSkipListMap
.Die ältere
SortedMap
Schnittstelle wird effektiv durch die neuereNavigableMap
Schnittstelle ersetzt. Möglicherweise finden Sie jedoch Implementierungen von Drittanbietern, die nur die ältere Schnittstelle implementieren.Natürliche Reihenfolge
Wenn Sie eine möchten
Map
, deren Paare in der „natürlichen Reihenfolge“ des Schlüssels angeordnet sind, verwenden SieTreeMap
oderConcurrentSkipListMap
. Der Begriff "natürliche Ordnung" bezeichnet die Klasse der SchlüsselgeräteComparable
. Der von dercompareTo
Methode zurückgegebene Wert wird zum Vergleich beim Sortieren verwendet.Kunden Bestellung
Wenn Sie eine benutzerdefinierte Sortierroutine für Ihre Schlüssel angeben möchten, die zum Verwalten einer sortierten Reihenfolge verwendet werden soll, übergeben Sie eine
Comparator
Implementierung, die der Klasse Ihrer Schlüssel entspricht. Verwenden Sie entwederTreeMap
oderConcurrentSkipListMap
, indem Sie Ihre übergebenComparator
.Ursprüngliche Einfügereihenfolge
Wenn Sie möchten, dass die Paare Ihrer Karte in der ursprünglichen Reihenfolge beibehalten werden, in der Sie sie in die Karte eingefügt haben, verwenden Sie
LinkedHashMap
.Aufzählungsdefinitionsreihenfolge
Wenn Sie eine Aufzählung wie
DayOfWeek
oderMonth
als Schlüssel verwenden, verwenden Sie dieEnumMap
Klasse. Diese Klasse ist nicht nur stark optimiert, um sehr wenig Speicher zu verbrauchen und sehr schnell zu laufen, sondern verwaltet Ihre Paare auch in der durch die Aufzählung festgelegten Reihenfolge. ZumDayOfWeek
Beispiel wird der Schlüssel vonDayOfWeek.MONDAY
zuerst gefunden, wenn er iteriert wird, und der Schlüssel vonDayOfWeek.SUNDAY
wird zuletzt sein.Andere Überlegungen
Map
Berücksichtigen Sie bei der Auswahl einer Implementierung auch Folgendes:Collections::synchronizedMap
(weniger bevorzugt).Diese beiden Überlegungen werden in der obigen Grafiktabelle behandelt.
quelle
EnumMap
, da es das erste Mal ist, dass ich davon gehört habe. Es gibt wahrscheinlich viele Fälle, in denen dies nützlich sein könnte.Die Reihenfolge hängt immer von der spezifischen Kartenimplementierung ab. Mit Java 8 können Sie eine der folgenden Methoden verwenden:
Oder:
Das Ergebnis ist das gleiche (gleiche Reihenfolge). Das von der Karte gesicherte entrySet, sodass Sie dieselbe Bestellung erhalten. Das zweite ist praktisch, da Sie damit Lambdas verwenden können, z. B. wenn Sie nur ganzzahlige Objekte drucken möchten, die größer als 5 sind:
Der folgende Code zeigt die Iteration durch LinkedHashMap und normale HashMap (Beispiel). Sie werden Unterschiede in der Reihenfolge sehen:
quelle
quelle
Verwenden Sie Java 8:
quelle
Sie können es mit Generika tun:
quelle
Eine effektive iterative Lösung über eine Karte ist eine "für jede" Schleife von Java 5 bis Java 7. Hier ist sie:
Ab Java 8 können Sie einen Lambda-Ausdruck verwenden, um eine Karte zu durchlaufen. Es ist ein erweitertes "forEach"
quelle
quelle
Es gibt viele Möglichkeiten, dies zu tun. Nachfolgend einige einfache Schritte:
Angenommen, Sie haben eine Karte wie:
Dann können Sie wie folgt vorgehen, um Kartenelemente zu durchlaufen.
quelle
quelle