web-dev-qa-db-fra.com

l'ordre d'itération keySet () de Java est-il cohérent?

Je comprends que l'ensemble renvoyé par la méthode keySet () d'une mappe ne garantit pas un ordre particulier.

Ma question est la suivante: garantit-il l'ordre même sur plusieurs itérations? Par exemple

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

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

...

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

Dans le code ci-dessus, en supposant que la carte soit non modifiée, l'itération sur les ensembles de clés sera-t-elle dans le même ordre. En utilisant le jdk15 de Sun, il fait itérer dans le même ordre, mais avant de dépendre de ce comportement, j'aimerais savoir si tous les JDK feront de même.

MODIFIER

Je vois dans les réponses que je ne peux pas en dépendre. Dommage. J'espérais m'en sortir sans avoir à construire une nouvelle collection pour garantir ma commande. Mon code devait parcourir, effectuer une certaine logique, puis parcourir à nouveau avec le même ordre. Je vais juste créer une nouvelle ArrayList à partir du keySet qui garantira l'ordre.

63
karoberts

Si cela n'est pas indiqué comme étant garanti dans la documentation de l'API, vous ne devriez pas en dépendre. Le comportement peut même changer d'une version du JDK à l'autre, même à partir du JDK du même fournisseur.

Vous pouvez facilement obtenir le jeu et ensuite le trier vous-même, non?

43
Ken Liu

Vous pouvez utiliser un LinkedHashMap si vous voulez un HashMap dont l'ordre d'itération ne change pas.

De plus, vous devriez toujours l'utiliser si vous parcourez la collection. Itérer sur entrySet ou keySet de HashMap est beaucoup plus lent que sur celui de LinkedHashMap.

45
ciamej

Map est seulement une interface (plutôt qu'une classe), ce qui signifie que la classe sous-jacente qui l'implémente (il y en a beaucoup) pourrait se comporter différemment, et le contrat pour keySet () dans l'API n'indique pas qu'une itération cohérente est requise. 

Si vous regardez une classe spécifique qui implémente Map (HashMap, LinkedHashMap, TreeMap, etc.), vous pourrez voir comment elle implémente la fonction keySet () afin de déterminer le comportement de ce dernier en recherchant le code source. Examinez de près l'algorithme pour voir si la propriété que vous recherchez est conservée (c'est-à-dire un ordre d'itération cohérent lorsque la carte n'a eu aucune insertion/suppression entre les itérations). La source de HashMap, par exemple, est la suivante (ouvrir JDK 6): http://www.docjar.com/html/api/Java/util/HashMap.Java.html

Cela peut varier énormément d'un JDK à l'autre, donc je ne compterais certainement pas dessus.

Cela étant dit, si vous avez vraiment besoin d'un ordre d'itération cohérent, vous pouvez essayer un LinkedHashMap.

9
mpobrien

L'API pour Map ne garantit pas aucune commande, même entre plusieurs invocations de la méthode sur le même objet.

En pratique, je serais très surpris que l'ordre des itérations change pour plusieurs invocations ultérieures (en supposant que la carte elle-même ne change pas entre les deux), mais vous ne devriez pas (et, selon l'API, ne peut pas) vous en tenir à cela.

EDIT - si vous voulez que l'ordre des itérations soit cohérent, vous voulez un SortedMap qui fournit exactement ces garanties.

6
Andrzej Doyle

Juste pour le plaisir, j'ai décidé d'écrire un code que vous pouvez utiliser pour garantir un ordre aléatoire à chaque fois. Ceci est utile pour que vous puissiez attraper des cas où vous êtes en fonction de la commande mais vous ne devriez pas l'être. Si vous voulez dépendre de la commande, comme d’autres l’ont dit, vous devez utiliser une SortedMap. Si vous utilisez simplement une carte et que vous vous fiez à l'ordre, l'utilisation de RandomIterator ci-dessous le détectera. Je ne l'utiliserais que dans les tests de code, car il utilise plus de mémoire que ne le ferait pas. 

Vous pouvez également envelopper la carte (ou l'ensemble) pour qu'ils renvoient le RandomeIterator qui vous permettra ensuite d'utiliser la boucle for-each.

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 ne garantit pas que l'ordre de la carte restera constant dans le temps.

3
Amir Afghani

Ce n'est pas obligé. La fonction keySet d'une mappe renvoie un ensemble et la méthode de son itérateur l'indique dans sa documentation:

"Retourne un itérateur sur les éléments de cet ensemble. Les éléments sont renvoyés sans ordre particulier (à moins que cet ensemble ne soit une instance d'une classe fournissant une garantie)."

Donc, sauf si vous utilisez une de ces classes avec une garantie, il n'y en a pas.

2
Jeff Storey

Map est une interface et ne définit pas dans la documentation cet ordre doit être identique. Cela signifie que vous ne pouvez pas compter sur la commande. Mais si vous contrôlez l'implémentation de la carte renvoyée par la méthode getMap (), vous pouvez utiliser LinkedHashMap ou TreeMap et obtenir le même ordre de clés/valeurs tout le temps que vous parcourez.

2
Andrey Adamovich

Logiquement, si le contrat indique "aucune commande particulière n'est garantie", et puisque "la commande dont elle est sortie une fois" est une commande particulière, la réponse est non, vous ne pouvez pas compter sur sa sortie. même façon deux fois.

1
Jonathan Feinberg

Je suis d'accord avec la chose LinkedHashMap. Il suffit de mettre mes découvertes et mon expérience à portée de main alors que je tentais de classer HashMap par clés. 

Mon code pour créer 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");

}

J'ai une fonction showMap qui affiche les entrées de la carte: 

public void showMap (Map<Integer, String> map1) {
    for (Map.Entry<Integer,  String> entry: map1.entrySet()) {
        System.out.println("[Key: "+entry.getKey()+ " , "+"Value: "+entry.getValue() +"] ");

    }

}

Maintenant, lorsque j'imprime la carte avant le tri, elle imprime la séquence suivante: 

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] 

Ce qui est fondamentalement différent de l'ordre dans lequel les clés de la carte ont été placées. 

Maintenant, quand je le trie avec les clés de la carte: 

    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);

la sortie est: 

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] 

Vous pouvez voir la différence dans l'ordre des clés. L'ordre des clés est correct, mais celui des clés de la carte copiée est encore dans le même ordre que celui de la carte précédente. Je ne sais pas si c'est valide à dire, mais pour deux hashmap avec les mêmes clés, l'ordre des clés est le même. Cela implique à la déclaration que l'ordre des clés n'est pas garanti mais peut être identique pour deux mappes avec les mêmes clés en raison de la nature inhérente de l'algorithme d'insertion de clé si HashMap implémente cette version de la JVM. 

Maintenant, lorsque j'utilise LinkedHashMap pour copier des entrées triées sur HashMap, j'obtiens le résultat souhaité (ce qui était naturel, mais ce n'est pas le point. Le point concerne l'ordre des clés de 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);

Sortie: 

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

Vous pouvez également stocker l'instance Set renvoyée par la méthode keySet () et l'utiliser chaque fois que vous avez besoin du même ordre.

0
Shivang Agarwal