Ich verstehe, dass das von der keySet () -Methode einer Map zurückgegebene Set keine bestimmte Reihenfolge garantiert.
Meine Frage ist, garantiert es die gleiche Reihenfolge über mehrere Iterationen. Zum Beispiel
Map<K,V> map = getMap();
for( K k : map.keySet() )
{
}
...
for( K k : map.keySet() )
{
}
Unter der Annahme, dass die Zuordnung nicht geändert wird, erfolgt im obigen Code die Iteration über die keySets in derselben Reihenfolge. Sun jdk15 Mit ihm tut Iterierte in der gleichen Reihenfolge, aber bevor ich auf dieses Verhalten abhängen, würde Ich mag wissen , ob alle JDKs das gleiche tun.
BEARBEITEN
Ich sehe aus den Antworten, dass ich mich nicht darauf verlassen kann. Schade. Ich hatte gehofft, nicht eine neue Kollektion bauen zu müssen, um meine Bestellung zu garantieren. Mein Code musste durchlaufen, logisch ausgeführt und dann mit derselben Reihenfolge erneut durchlaufen werden. Ich werde einfach eine neue ArrayList aus dem keySet erstellen, die die Reihenfolge garantiert.
.keySet()
und.values()
konsistent ist. Leider ist die akzeptierte Antwort falsch. @karoberts - kannst du bitte einen Blick darauf werfen?Antworten:
Wenn in der API-Dokumentation nicht angegeben ist, dass dies garantiert ist, sollten Sie sich nicht darauf verlassen. Das Verhalten kann sich sogar von einer Version des JDK zur nächsten ändern, selbst vom JDK desselben Anbieters.
Sie könnten das Set leicht bekommen und es dann einfach selbst sortieren, oder?
quelle
Sie können eine LinkedHashMap verwenden, wenn Sie eine HashMap möchten, deren Iterationsreihenfolge sich nicht ändert.
Außerdem sollten Sie es immer verwenden, wenn Sie die Sammlung durchlaufen. Das Iterieren über HashMaps entrySet oder keySet ist viel langsamer als über LinkedHashMaps.
quelle
Map ist nur eine Schnittstelle (und keine Klasse). Dies bedeutet, dass sich die zugrunde liegende Klasse, die sie implementiert (und es gibt viele), möglicherweise anders verhält, und der Vertrag für keySet () in der API gibt nicht an, dass eine konsistente Iteration erforderlich ist.
Wenn Sie sich eine bestimmte Klasse ansehen, die Map implementiert (HashMap, LinkedHashMap, TreeMap usw.), können Sie sehen, wie sie die Funktion keySet () implementiert, um das Verhalten durch Auschecken der Quelle zu bestimmen Schauen Sie sich den Algorithmus genau an, um festzustellen, ob die gesuchte Eigenschaft erhalten bleibt (dh eine konsistente Iterationsreihenfolge, wenn die Karte zwischen den Iterationen keine Einfügungen / Entfernungen aufweist). Die Quelle für HashMap befindet sich beispielsweise hier (öffnen Sie JDK 6): http://www.docjar.com/html/api/java/util/HashMap.java.html
Es könnte von einem JDK zum nächsten sehr unterschiedlich sein, also würde ich mich definitiv nicht darauf verlassen.
Wenn Sie jedoch eine konsistente Iterationsreihenfolge benötigen, sollten Sie eine LinkedHashMap ausprobieren.
quelle
Die API für Map garantiert keinerlei Reihenfolge, auch nicht zwischen mehreren Aufrufen der Methode für dasselbe Objekt.
In der Praxis wäre ich sehr überrascht, wenn sich die Iterationsreihenfolge für mehrere nachfolgende Aufrufe ändern würde (vorausgesetzt, die Karte selbst hat sich dazwischen nicht geändert) - aber Sie sollten sich nicht darauf verlassen (und laut API nicht).
BEARBEITEN - Wenn Sie sich darauf verlassen möchten, dass die Iterationsreihenfolge konsistent ist, möchten Sie eine SortedMap, die genau diese Garantien bietet.
quelle
Nur zum Spaß habe ich beschlossen, einen Code zu schreiben, mit dem Sie jedes Mal eine zufällige Reihenfolge garantieren können. Dies ist nützlich, damit Sie Fälle abfangen können, in denen Sie von der Bestellung abhängig sind, dies aber nicht sein sollten. Wenn Sie von der Reihenfolge abhängig sein möchten, sollten Sie, wie bereits erwähnt, eine SortedMap verwenden. Wenn Sie nur eine Karte verwenden und sich zufällig auf die Reihenfolge verlassen, wird dies mit dem folgenden RandomIterator erfasst. Ich würde es nur zum Testen von Code verwenden, da es mehr Speicher benötigt, als dies nicht der Fall wäre.
Sie können auch die Map (oder das Set) umbrechen, damit sie den RandomeIterator zurückgeben, mit dem Sie dann die for-each-Schleife verwenden können.
import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class Main { private Main() { } public static void main(final String[] args) { final Map<String, String> items; items = new HashMap<String, String>(); items.put("A", "1"); items.put("B", "2"); items.put("C", "3"); items.put("D", "4"); items.put("E", "5"); items.put("F", "6"); items.put("G", "7"); display(items.keySet().iterator()); System.out.println("---"); display(items.keySet().iterator()); System.out.println("---"); display(new RandomIterator<String>(items.keySet().iterator())); System.out.println("---"); display(new RandomIterator<String>(items.keySet().iterator())); System.out.println("---"); } private static <T> void display(final Iterator<T> iterator) { while(iterator.hasNext()) { final T item; item = iterator.next(); System.out.println(item); } } } class RandomIterator<T> implements Iterator<T> { private final Iterator<T> iterator; public RandomIterator(final Iterator<T> i) { final List<T> items; items = new ArrayList<T>(); while(i.hasNext()) { final T item; item = i.next(); items.add(item); } Collections.shuffle(items); iterator = items.iterator(); } public boolean hasNext() { return (iterator.hasNext()); } public T next() { return (iterator.next()); } public void remove() { iterator.remove(); } }
quelle
Ich stimme der Sache mit LinkedHashMap zu. Ich habe nur meine Erkenntnisse und Erfahrungen zusammengestellt, als ich vor dem Problem stand, als ich versuchte, HashMap nach Schlüsseln zu sortieren.
Mein Code zum Erstellen von HashMap:
HashMap<Integer, String> map; @Before public void initData() { map = new HashMap<>(); map.put(55, "John"); map.put(22, "Apple"); map.put(66, "Earl"); map.put(77, "Pearl"); map.put(12, "George"); map.put(6, "Rocky"); }
Ich habe eine Funktion showMap, die Einträge der Karte druckt:
public void showMap (Map<Integer, String> map1) { for (Map.Entry<Integer, String> entry: map1.entrySet()) { System.out.println("[Key: "+entry.getKey()+ " , "+"Value: "+entry.getValue() +"] "); } }
Wenn ich jetzt die Karte vor dem Sortieren drucke, wird die folgende Reihenfolge gedruckt:
Map before sorting : [Key: 66 , Value: Earl] [Key: 22 , Value: Apple] [Key: 6 , Value: Rocky] [Key: 55 , Value: John] [Key: 12 , Value: George] [Key: 77 , Value: Pearl]
Dies unterscheidet sich grundlegend von der Reihenfolge, in der die Kartenschlüssel eingegeben wurden.
Wenn ich es jetzt mit Kartenschlüsseln sortiere:
List<Map.Entry<Integer, String>> entries = new ArrayList<>(map.entrySet()); Collections.sort(entries, new Comparator<Entry<Integer, String>>() { @Override public int compare(Entry<Integer, String> o1, Entry<Integer, String> o2) { return o1.getKey().compareTo(o2.getKey()); } }); HashMap<Integer, String> sortedMap = new LinkedHashMap<>(); for (Map.Entry<Integer, String> entry : entries) { System.out.println("Putting key:"+entry.getKey()); sortedMap.put(entry.getKey(), entry.getValue()); } System.out.println("Map after sorting:"); showMap(sortedMap);
Die Ausgabe lautet:
Sorting by keys : Putting key:6 Putting key:12 Putting key:22 Putting key:55 Putting key:66 Putting key:77 Map after sorting: [Key: 66 , Value: Earl] [Key: 6 , Value: Rocky] [Key: 22 , Value: Apple] [Key: 55 , Value: John] [Key: 12 , Value: George] [Key: 77 , Value: Pearl]
Sie können den Unterschied in der Reihenfolge der Schlüssel sehen. Die sortierte Reihenfolge der Schlüssel ist in Ordnung, aber die der Schlüssel der kopierten Karte liegt wieder in der gleichen Reihenfolge wie die frühere Karte. Ich weiß nicht, ob dies gültig ist, aber für zwei Hashmaps mit denselben Schlüsseln ist die Reihenfolge der Schlüssel gleich. Dies impliziert die Aussage, dass die Reihenfolge der Schlüssel nicht garantiert ist, aber für zwei Maps mit denselben Schlüsseln aufgrund der inhärenten Natur des Algorithmus zum Einfügen von Schlüsseln gleich sein kann, wenn die HashMap-Implementierung dieser JVM-Version erfolgt.
Wenn ich jetzt LinkedHashMap verwende, um sortierte Einträge in HashMap zu kopieren, erhalte ich das gewünschte Ergebnis (was natürlich war, aber das ist nicht der Punkt. Der Punkt betrifft die Reihenfolge der Schlüssel von HashMap).
HashMap<Integer, String> sortedMap = new LinkedHashMap<>(); for (Map.Entry<Integer, String> entry : entries) { System.out.println("Putting key:"+entry.getKey()); sortedMap.put(entry.getKey(), entry.getValue()); } System.out.println("Map after sorting:"); showMap(sortedMap);
Ausgabe:
Sorting by keys : Putting key:6 Putting key:12 Putting key:22 Putting key:55 Putting key:66 Putting key:77 Map after sorting: [Key: 6 , Value: Rocky] [Key: 12 , Value: George] [Key: 22 , Value: Apple] [Key: 55 , Value: John] [Key: 66 , Value: Earl] [Key: 77 , Value: Pearl]
quelle
Hashmap garantiert nicht, dass die Reihenfolge der Karte über die Zeit konstant bleibt.
quelle
Das muss nicht sein. Die keySet-Funktion einer Karte gibt ein Set zurück, und die Iteratormethode des Sets sagt dies in der Dokumentation aus:
"Gibt einen Iterator über die Elemente in diesem Satz zurück. Die Elemente werden in keiner bestimmten Reihenfolge zurückgegeben (es sei denn, dieser Satz ist eine Instanz einer Klasse, die eine Garantie bietet)."
Wenn Sie also keine dieser Klassen mit Garantie verwenden, gibt es keine.
quelle
Map ist eine Schnittstelle und definiert in der Dokumentation nicht, dass die Reihenfolge identisch sein soll. Das bedeutet, dass Sie sich nicht auf die Bestellung verlassen können. Wenn Sie jedoch die von getMap () zurückgegebene Map-Implementierung steuern, können Sie LinkedHashMap oder TreeMap verwenden und bei jeder Iteration dieselbe Reihenfolge von Schlüsseln / Werten abrufen.
quelle
tl; dr Ja.
Ich glaube, die Iterationsreihenfolge für
.keySet()
und.values()
ist konsistent (Java 8).Beweis 1 : Wir laden a
HashMap
mit zufälligen Schlüsseln und zufälligen Werten. Wir wiederholen diesHashMap
mit.keySet()
und laden die Schlüssel und die entsprechenden Werte zu aLinkedHashMap
(die Reihenfolge der eingefügten Schlüssel und Werte bleibt erhalten). Dann vergleichen wir die.keySet()
beiden Karten und.values()
beide Karten. Es kommt immer gleich heraus, scheitert nie.public class Sample3 { static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; static SecureRandom rnd = new SecureRandom(); // from here: https://stackoverflow.com/a/157202/8430155 static String randomString(int len){ StringBuilder sb = new StringBuilder(len); for (int i = 0; i < len; i++) { sb.append(AB.charAt(rnd.nextInt(AB.length()))); } return sb.toString(); } public static void main(String[] args) throws Exception { for (int j = 0; j < 10; j++) { Map<String, String> map = new HashMap<>(); Map<String, String> linkedMap = new LinkedHashMap<>(); for (int i = 0; i < 1000; i++) { String key = randomString(8); String value = randomString(8); map.put(key, value); } for (String k : map.keySet()) { linkedMap.put(k, map.get(k)); } if (!(map.keySet().toString().equals(linkedMap.keySet().toString()) && map.values().toString().equals(linkedMap.values().toString()))) { // never fails System.out.println("Failed"); break; } } } }
Beweis 2 : Von hier aus ist das
table
ein Array vonNode<K,V>
Klassen. Wir wissen, dass das Iterieren eines Arrays jedes Mal das gleiche Ergebnis liefert./** * The table, initialized on first use, and resized as * necessary. When allocated, length is always a power of two. * (We also tolerate length zero in some operations to allow * bootstrapping mechanics that are currently not needed.) */ transient Node<K,V>[] table;
Die Klasse, die verantwortlich ist für
.values()
:final class Values extends AbstractCollection<V> { // more code here public final void forEach(Consumer<? super V> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.value); } if (modCount != mc) throw new ConcurrentModificationException(); } } }
Die Klasse, die verantwortlich ist für
.keySet()
:final class KeySet extends AbstractSet<K> { // more code here public final void forEach(Consumer<? super K> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } }
Schauen Sie sich beide inneren Klassen genau an. Sie sind ziemlich gleich, außer:
if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); <- from KeySet class // action.accept(e.value); <- the only change from Values class } if (modCount != mc) throw new ConcurrentModificationException(); }
Sie iterieren auf demselben Array
table
, um es.keySet()
in derKeySet
Klasse und.values()
in derValues
Klasse zu unterstützen.Beweis 3: In dieser Antwort wird auch explizit angegeben: Ja, keySet (), values () und entrySet () geben Werte in der Reihenfolge zurück, in der die interne verknüpfte Liste verwendet wird.
Daher sind die
.keySet()
und.values()
konsistent.quelle
Wenn der Vertrag besagt, dass "keine bestimmte Bestellung garantiert ist" und "die Bestellung, die einmal ausgeführt wurde" eine bestimmte Bestellung ist , lautet die Antwort logischerweise "Nein". Sie können sich nicht darauf verlassen, dass sie zweimal auf dieselbe Weise ausgeführt wird.
quelle
Sie können auch die von der keySet () -Methode zurückgegebene Set-Instanz speichern und diese Instanz verwenden, wenn Sie dieselbe Reihenfolge benötigen.
quelle
iterator()
einen Iterator mit einer anderen Iterationsreihenfolge zurückgeben, selbst auf demselben Satz?