web-dev-qa-db-de.com

ist die Java HashMap keySet () - Iterationsreihenfolge konsistent?

Ich verstehe, dass das von der keySet () -Methode einer Map zurückgegebene Set keine bestimmte Reihenfolge garantiert.

Meine Frage ist, ob die Reihenfolge same über mehrere Iterationen gewährleistet ist. Zum Beispiel

Map<K,V> map = getMap();

for( K k : map.keySet() )
{
}

...

for( K k : map.keySet() )
{
}

Wenn im obigen Code angenommen wird, dass die Map nicht geändert ist, wird die Iteration über die KeySets in derselben Reihenfolge sein. Mit Suns jdk15 iteriert it in der gleichen Reihenfolge, aber bevor ich von diesem Verhalten abhänge, möchte ich wissen, ob alle JDKs dasselbe tun.

EDIT

Ich sehe aus den Antworten, dass ich mich nicht darauf verlassen kann. Schade. Ich hatte gehofft, dass ich keine neue Kollektion bauen musste, um meine Bestellung zu garantieren. Mein Code musste durchlaufen, etwas Logik durchlaufen und dann mit derselben Reihenfolge erneut durchlaufen werden. Ich erstelle einfach eine neue ArrayList aus dem keySet, die die Reihenfolge garantiert.

63
karoberts

Wenn es in der API-Dokumentation nicht als garantiert angegeben wird, sollten Sie sich nicht darauf verlassen. Das Verhalten kann sich sogar von einer Version des JDK zur nächsten ändern, selbst vom JDK des gleichen Herstellers.

Sie könnten das Set leicht bekommen und es dann einfach selbst sortieren, richtig?

43
Ken Liu

Sie können eine LinkedHashMap verwenden, wenn Sie eine HashMap wünschen, 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 LinkedHashMap's.

45
ciamej

Map ist nur eine Schnittstelle (und nicht eine Klasse), was bedeutet, dass die zugrunde liegende Klasse, die sie implementiert (und viele), sich anders verhalten kann, und der Vertrag für keySet () in der API bedeutet nicht, 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 keySet () - Funktion implementiert, um zu bestimmen, wie das Verhalten aussehen würde, wenn Sie die Quelle auschecken Sehen Sie sich den Algorithmus genau an, um zu sehen, ob die Eigenschaft, nach der Sie suchen, erhalten bleibt (d. h. konsistente Iterationsreihenfolge, wenn die Karte keine Einfügungen/Entfernungen zwischen den Iterationen hatte). Die Quelle für HashMap ist beispielsweise hier (offenes JDK 6): http://www.docjar.com/html/api/Java/util/HashMap.Java.html

Es kann von einem JDK zum nächsten sehr unterschiedlich sein, daher würde ich mich definitiv nicht darauf verlassen.

Das heißt, wenn Sie eine konsistente Iterationsreihenfolge wirklich benötigen, können Sie eine LinkedHashMap ausprobieren.

9
mpobrien

Die API für Map garantiert nicht die Reihenfolge von any, 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 aufeinanderfolgende Aufrufe ändert (vorausgesetzt, die Karte selbst hat sich nicht zwischendurch geändert) - aber Sie sollten sich nicht (und laut API nicht) darauf verlassen.

EDIT - Wenn Sie sich darauf verlassen wollen, dass die Iterationsreihenfolge konsistent ist, dann möchten Sie eine SortedMap , die genau diese Garantien bietet.

6
Andrzej Doyle

Aus Spaß habe ich mich dazu entschlossen, Code zu schreiben, mit dem Sie jedes Mal eine zufällige Reihenfolge gewährleisten 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 sollte. Wenn Sie sich auf die Reihenfolge verlassen wollen, sollten Sie, wie andere bereits gesagt haben, eine SortedMap verwenden. Wenn Sie nur eine Map verwenden und sich auf die Reihenfolge verlassen, wird dies durch den folgenden RandomIterator festgestellt. Ich würde es nur beim Testen von Code verwenden, da hier mehr Speicher verwendet wird, als dies nicht der Fall wäre. 

Sie können die Map (oder das Set) auch umschließen, damit der RandomeIterator zurückgegeben wird, mit dem Sie 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();
    }
}
5
TofuBeer

Hashmap garantiert nicht, dass die Reihenfolge der Karte mit der Zeit konstant bleibt.

3
Amir Afghani

Das muss nicht sein. Die keySet-Funktion einer Map gibt ein Set zurück, und die Iterator-Methode des Sets sagt dies in ihrer Dokumentation aus:

"Gibt einen Iterator über die Elemente in dieser Gruppe zurück. Die Elemente werden in keiner bestimmten Reihenfolge zurückgegeben (es sei denn, diese Gruppe ist eine Instanz einer Klasse, die eine Garantie bietet)."

Wenn Sie also keine dieser Klassen mit einer Garantie verwenden, gibt es keine.

2
Jeff Storey

Map ist eine Schnittstelle und definiert in der Dokumentation nicht, dass die Reihenfolge identisch sein sollte. 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, um die gleiche Reihenfolge der Schlüssel/Werte zu erhalten, wenn Sie sie durchlaufen.

2

Logischerweise, wenn der Vertrag sagt "keine bestimmte Bestellung ist garantiert", und da "die Bestellung einmal herausgekommen ist" eine bestimmte Bestellung ist, dann ist die Antwort nein, Sie können sich nicht darauf verlassen, dass sie die gleich zweimal.

1

Ich stimme der LinkedHashMap-Sache zu. Ich stelle einfach meine Erkenntnisse und Erfahrungen ein, während ich auf das Problem stieß, 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 nun 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 grundsätzlich von der Reihenfolge, in der die Kartenschlüssel eingegeben wurden. 

Wenn ich es jetzt mit Kartentasten 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);

das Ergebnis ist: 

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 derselben Reihenfolge wie die vorherige Karte. Ich weiß nicht, ob dies gültig ist, aber für zwei Hashmaps mit den gleichen Schlüsseln ist die Reihenfolge der Schlüssel gleich. Dies bedeutet für die Anweisung, dass die Reihenfolge der Schlüssel nicht garantiert werden kann, aber aufgrund der inhärenten Natur des Schlüsseleinfügungsalgorithmus bei der HashMap-Implementierung dieser JVM-Version für zwei Maps mit den gleichen Schlüsseln identisch sein kann. 

Wenn ich nun LinkedHashMap zum Kopieren von sortierten Einträgen in HashMap verwende, erhalte ich das gewünschte Ergebnis (was natürlich war, aber das ist nicht der Punkt. Punkt bezieht sich auf 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] 
0
user2214297

Sie können auch die Set-Instanz speichern, die von der keySet () -Methode zurückgegeben wird, und diese Instanz verwenden, wenn Sie dieselbe Reihenfolge benötigen.

0
Shivang Agarwal