web-dev-qa-db-fra.com

Pourquoi la méthode get de HashMap a-t-elle une boucle FOR?

Je regarde le code source de HashMap in Java 7, et je vois que la méthode put vérifiera si une entrée est déjà présente et si elle est présent alors il remplacera l'ancienne valeur par la nouvelle valeur.

    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

Donc, fondamentalement, cela signifie qu’il n’y aura toujours qu’une entrée pour une clé donnée; j’ai également vu cela en procédant au débogage, mais si je me trompe, corrigez-moi.

Maintenant, puisqu'il n'y a qu'une seule entrée pour une clé donnée, pourquoi la méthode get a-t-elle une boucle FOR, puisqu'elle aurait simplement retourné la valeur directement?

    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
            return e.value;
    }

Je pense que la boucle ci-dessus est inutile. S'il vous plaît, aidez-moi à comprendre si je me trompe.

46
pjj

table[indexFor(hash, table.length)] est le compartiment du HashMap pouvant contenir la clé recherchée (si elle est présente dans le Map).

Cependant, chaque compartiment peut contenir plusieurs entrées (soit différentes clés ayant le même hashCode(), soit différentes clés avec différentes hashCode() toujours affectées au même compartiment). Vous devez donc effectuer une itération. ces entrées jusqu'à ce que vous trouviez la clé que vous recherchez.

Le nombre d'entrées attendu dans chaque compartiment devant être très petit, cette boucle est toujours exécutée dans le temps attendu O(1).

62
Eran

Si vous voyez le fonctionnement interne de la méthode get de HashMap.

public V get(Object key)  {
        if (key == null)
           return getForNullKey();
         int hash = hash(key.hashCode());
         for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) 
         {
             Object k;
             if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                 return e.value;
         }
             return null;
}
  • D'abord, il obtient le code de hachage de l'objet clé, qui est transmis, et trouve l'emplacement du compartiment.
  • Si le compartiment correct est trouvé, il renvoie la valeur (e.value).
  • Si aucune correspondance n'est trouvée, la valeur null est renvoyée.

Il peut parfois y avoir des risques de collision entre Hashcode et pour résoudre cette collision, Hashmap utilise equals (), puis stocke cet élément dans LinkedList dans le même compartiment.

Prenons par exemple: enter image description here

Récupérez les données pour la clé vaibahv: map.get (nouvelle clé ("vaibhav"));

Pas:

  1. Calculez le code de hachage de la clé {"vaibhav"}. Il sera généré sous la forme 118.

  2. Calculer l'indice en utilisant la méthode de l'indice, il sera 6.

  3. Accédez à l’index 6 du tableau et comparez la clé du premier élément avec la clé donnée. Si les deux sont égaux, alors renvoyez la valeur, sinon, vérifiez si l'élément suivant existe.

  4. Dans notre cas, il ne se trouve pas en tant que premier élément et l'objet suivant de noeud n'est pas null.

  5. Si next of node est null, retourne null.

  6. Si next of node n'est pas null, passez au deuxième élément et répétez le processus 3 jusqu'à ce que key ne soit pas trouvé ou que next ne soit pas nul.

Pour ce processus de récupération pour la boucle sera utilisé. Pour plus de référence, vous pouvez vous référer this

18
Prashant Patil

Pour mémoire, en Java-8, cela est également présent (en quelque sorte, car il y a aussi TreeNodes également):

if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }

En gros (pour le cas où le bin n'est pas un Tree), itérez le bin entier, jusqu'à ce que vous trouviez l'entrée que vous recherchez.

En regardant cette implémentation, vous comprendrez peut-être pourquoi il est bon de fournir un bon hash - de sorte que toutes les entrées ne se retrouvent pas dans le même seau, ce qui laisse plus de temps pour la rechercher.

5
Eugene

Tandis que les autres réponses expliquent ce qui se passe, les commentaires d'OP sur ces réponses m'amènent à penser qu'un angle d'explication différent est nécessaire.

Exemple simplifié

Disons que vous allez lancer 10 chaînes dans une carte de hachage: "A", "B", "C", "Bonjour", "Au revoir", "Yo", "Yo-yo", "Z", "1 "," 2 "

Vous utilisez HashMap comme carte de hachage au lieu de créer votre propre carte de hachage (bon choix). Certains des éléments ci-dessous n'utiliseront pas HashMap implémentation directement, mais l'approcheront d'un point de vue plus théorique et abstrait.

HashMap ne sait pas, comme par magie, que vous allez lui ajouter 10 chaînes, ni quelles chaînes vous y ajouterez plus tard. Il doit fournir des emplacements pour mettre tout ce que vous pourriez lui donner ... sachant que vous allez y mettre 100 000 chaînes - peut-être tous les mots du dictionnaire.

Disons que, à cause de l'argument de constructeur que vous avez choisi lors de la création de votre new HashMap(n), votre hash map a 20 buckets. Nous les appellerons bucket[0] Via bucket[19].

  1. map.put("A", value); Supposons que la valeur de hachage pour "A" soit 5. La carte de hachage peut maintenant faire bucket[5] = new Entry("A", value);

  2. map.put("B", value); Supposer un hachage ("B") = 3. Donc, bucket[3] = new Entry("B", value);

  3. map.put("C"), value); - dièse ("C") = 19 - bucket[19] = new Entry("C", value);

  4. map.put("Hi", value); Maintenant, voici où cela devient intéressant. Disons que votre fonction de hachage est telle que hash ("Hi") = 3. Alors maintenant, hash map veut faire bucket[3] = new Entry("Hi", value);Nous avons un problème!bucket[3] est où nous mettons la clé "B", et "Hi" est certainement une clé différente de "B" ... mais ils ont le même valeur de hachage. Nous avons une collision !

En raison de cette possibilité, le HashMap n'est pas réellement implémenté de cette façon. ne carte de hachage doit avoir des compartiments pouvant contenir plus d'une entrée. NOTE: J'ai fait pas en dire plus que 1 entrée avec la même clé , comme nous ne pouvons pas avoir cela , mais il doit avoir des compartiments pouvant contenir plus d'une entrée de clés différentes . Nous avons besoin d'un seau pouvant contenir "B" et "Bonjour".

Donc, ne faisons pas bucket[n] = new Entry(key, value);, mais plutôt que bucket soit de type Bucket[] Au lieu de Entry[]. Alors maintenant, nous faisons bucket[n].add( new Entry(key, value) );

Alors changeons en ...

bucket[3].add("B", value);

et

bucket[3].add("Hi", value);

Comme vous pouvez le constater, nous avons maintenant les entrées pour "B" et "Hi" dans le même compartiment . Maintenant, lorsque nous voulons les récupérer, nous devons parcourir tout le seau, , par exemple, avec une boucle for .

Le bouclage est donc présent à cause des collisions. Pas collisions de key, mais collisions de hash(key).

Pourquoi utilisons-nous une structure de données aussi folle?

Vous pourriez demander à ce stade, "Attendez, QUOI!?! Pourquoi ferions-nous une chose aussi étrange comme ça ??? Pourquoi utilisons-nous une structure de données aussi complexe et compliquée ???" La réponse à cette question serait ...

Une carte de hachage fonctionne comme ceci à cause des propriétés qu'une configuration si particulière nous fournit en raison de la façon dont les calculs sont faits. Si vous utilisez une bonne fonction de hachage qui minimise les conflits et si vous redimensionnez votre HashMap pour avoir plus de compartiments que le nombre d'entrées que vous devinez y sera, alors vous avez une carte de hachage optimisée qui sera la structure de données la plus rapide pour les insertions et les requêtes de données complexes.

Votre HashMap est peut-être trop petit

Puisque vous dites que vous voyez souvent cette boucle-boucle être itérée avec plusieurs éléments dans votre débogage, cela signifie que votre HashMap est peut-être trop petit. Si vous avez une idée raisonnable du nombre d'éléments que vous pourriez y ajouter, essayez de définir une taille supérieure. Notez dans mon exemple ci-dessus que j'insérais 10 chaînes mais que j'avais une carte de hachage avec 20 compartiments. Avec une bonne fonction de hachage, cela produira très peu de collisions.

Remarque:

Remarque: l'exemple ci-dessus est une simplification de la question et prend quelques raccourcis par souci de brièveté. Une explication complète est même légèrement plus compliquée, mais tout ce que vous devez savoir pour répondre à la question posée se trouve ici.

2
Loduwijk

Les tables de hachage ont des compartiments, car les hachages d'objets ne doivent pas nécessairement être uniques. Si les hachages des objets sont égaux, les moyens, les objets, probablement, sont égaux. Si les hachages d'objets sont différents, les objets sont exactement différents. Par conséquent, les objets avec les mêmes hachages sont regroupés dans des compartiments. La boucle for est utilisée pour itérer les objets contenus dans un tel compartiment.

En fait, cela signifie que la complexité algorithmique de la recherche d'un objet dans une telle table de hachage n'est pas constante (bien que très proche de celui-ci), mais se situe entre logarithmique et linéaire.

1
Nikolay Lebedev

Je voudrais le dire avec des mots simples. la méthode put a une boucle FOR pour parcourir la liste des clés qui tombe dans le même compartiment que hashCode.

Que se passe-t-il lorsque vous faites put la paire key-value Dans le hashmap:

  1. Donc, pour chaque key que vous transmettez au HashMap, il calculera le hashCode correspondant.
  2. Tant de keys peuvent tomber dans le même compartiment hashCode. HashMap va maintenant vérifier si le même key est déjà présent ou non dans le même compartiment.
  3. Dans Java 7, HashMap conserve toutes les clés du même compartiment dans une liste. Par conséquent, avant d'insérer la clé, il parcourt la liste pour vérifier si la même clé est présente ou non. C'est pourquoi il y a une boucle FOR.

Donc, dans le cas moyen, sa complexité temporelle est: O(1) et, dans le pire des cas, sa complexité temporelle est O(N).

0
vkrishna17