Comment un Java HashMap gère-t-il différents objets avec le même code de hachage?


223

Selon ma compréhension, je pense:

  1. Il est parfaitement légal que deux objets aient le même code de hachage.
  2. Si deux objets sont égaux (en utilisant la méthode equals ()) alors ils ont le même hashcode.
  3. Si deux objets ne sont pas égaux, ils ne peuvent pas avoir le même code de hachage

Ai-je raison?

Maintenant, si je me trompe, j'ai la question suivante: Le HashMaputilise en interne le hashcode de l'objet. Donc, si deux objets peuvent avoir le même code de hachage, comment peut-il HashMapsuivre la clé qu'il utilise?

Quelqu'un peut-il expliquer comment l' HashMapinterne utilise le hashcode de l'objet?


29
Pour mémoire: # 1 et # 2 sont corrects, # 3 est faux: deux objets qui ne sont pas égaux peuvent avoir le même code de hachage.
Joachim Sauer

6
# 1 et # 3 sont même contradictoires
Delfic

En effet, si # 2 n'est pas suivi, alors l'implémentation equals () (ou sans doute le hashCode ()) est incorrecte.
Joachim Sauer

Réponses:


346

Un hashmap fonctionne comme ceci (c'est un peu simplifié, mais il illustre le mécanisme de base):

Il a un certain nombre de "compartiments" qu'il utilise pour stocker les paires clé-valeur. Chaque compartiment a un numéro unique - c'est ce qui identifie le compartiment. Lorsque vous placez une paire clé-valeur dans la carte, la table de hachage examine le code de hachage de la clé et stocke la paire dans le compartiment dont l'identifiant est le code de hachage de la clé. Par exemple: Le code de hachage de la clé est 235 -> la paire est stockée dans le numéro de compartiment 235. (Notez qu'un compartiment peut stocker plus d'une paire clé-valeur).

Lorsque vous recherchez une valeur dans la table de hachage, en lui donnant une clé, elle examinera d'abord le code de hachage de la clé que vous avez donnée. La table de hachage examinera ensuite le compartiment correspondant, puis comparera la clé que vous avez donnée avec les clés de toutes les paires du compartiment, en les comparant avec equals().

Vous pouvez maintenant voir comment cela est très efficace pour rechercher des paires clé-valeur dans une carte: par le code de hachage de la clé, la table de hachage sait immédiatement dans quel compartiment chercher, de sorte qu'elle n'a qu'à tester par rapport à ce qu'elle contient.

En regardant le mécanisme ci-dessus, vous pouvez également voir quelles exigences sont nécessaires sur les méthodes hashCode()et les equals()clés:

  • Si deux clés sont identiques ( equals()renvoie truelorsque vous les comparez), leur hashCode()méthode doit renvoyer le même nombre. Si les clés ne le respectent pas, les clés égales peuvent être stockées dans différents compartiments et le hashmap ne pourra pas trouver de paires clé-valeur (car il va chercher dans le même compartiment).

  • Si deux clés sont différentes, peu importe que leurs codes de hachage soient identiques ou non. Ils seront stockés dans le même compartiment si leurs codes de hachage sont les mêmes, et dans ce cas, la table de hachage les utilisera equals()pour les différencier.


4
vous avez écrit "et le hashmap ne serait pas en mesure de trouver des paires clé-valeur (car il va chercher dans le même compartiment)." Pouvez-vous expliquer qu'il va regarder dans le même seau dire que ces deux objets égaux sont t1 et t2 et qu'ils sont égaux et t1 et t2 ont respectivement les codes de hachage h1 et h2. Donc t1.equals (t2) = true et h1! = H2 Ainsi, lorsque le hashmap rechercherait t1, il cherchera dans le seau h1 et pour t2 dans le seau t2?
Geek

19
Si deux clés sont égales mais que leur hashCode()méthode renvoie des codes de hachage différents, les méthodes equals()et hashCode()de la classe de clé violent le contrat et vous obtiendrez des résultats étranges lorsque vous utilisez ces clés dans a HashMap.
Jesper

Chaque compartiment peut avoir plusieurs paires de valeurs clés, qui sont des listes liées en interne. Mais ma confusion est - qu'est-ce que le seau ici? Quelle structure de données utilise-t-elle en interne? Y a-t-il un lien entre les godets?
Ankit Sharma

1
@AnkitSharma Si vous voulez vraiment connaître tous les détails, recherchez le code source de HashMap, que vous pouvez trouver dans le fichier src.zipdans votre répertoire d'installation JDK.
Jesper

1
@ 1290 La seule relation entre les clés d'un même compartiment est qu'elles ont le même code de hachage.
Jesper

88

Votre troisième affirmation est incorrecte.

Il est parfaitement légal que deux objets inégaux aient le même code de hachage. Il est utilisé par HashMapcomme "filtre de premier passage" afin que la carte puisse trouver rapidement les entrées possibles avec la clé spécifiée. Les clés avec le même code de hachage sont ensuite testées pour leur égalité avec la clé spécifiée.

Vous ne voudriez pas que deux objets inégaux ne puissent pas avoir le même code de hachage, sinon cela vous limiterait à 2 32 objets possibles. (Cela signifierait également que différents types ne pourraient même pas utiliser les champs d'un objet pour générer des codes de hachage, car d'autres classes pourraient générer le même hachage.)


6
comment es-tu arrivé à 2 ^ 32 objets possibles?
Geek

5
Je suis en retard, mais pour ceux qui se demandent encore: Un hashcode en Java est un int, et un int a 2 ^ 32 valeurs possibles
Xerus

69

Diagramme de structure HashMap

HashMapest un tableau d' Entryobjets.

Considérez HashMapsimplement un tableau d'objets.

Jetez un œil à ce que Objectc'est:

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
 
}

Chaque Entryobjet représente une paire clé-valeur. Le champ nextfait référence à un autre Entryobjet si un compartiment en contient plusieurs Entry.

Parfois, il peut arriver que les codes de hachage pour 2 objets différents soient identiques. Dans ce cas, deux objets seront enregistrés dans un seul compartiment et seront présentés sous forme de liste liée. Le point d'entrée est l'objet le plus récemment ajouté. Cet objet fait référence à un autre objet avec le nextchamp et ainsi de suite. La dernière entrée fait référence à null.

Lorsque vous créez un HashMapavec le constructeur par défaut

HashMap hashMap = new HashMap();

Le tableau est créé avec une taille 16 et un équilibre de charge par défaut de 0,75.

Ajout d'une nouvelle paire valeur / clé

  1. Calculer le code de hachage pour la clé
  2. Calculer la position hash % (arrayLength-1)où l'élément doit être placé (numéro de godet)
  3. Si vous essayez d'ajouter une valeur avec une clé qui a déjà été enregistrée HashMap, la valeur est remplacée.
  4. Sinon, l'élément est ajouté au compartiment.

Si le seau a déjà au moins un élément, un nouveau est ajouté et placé dans la première position du seau. Son nextchamp fait référence à l'ancien élément.

Effacement

  1. Calculer le code de hachage pour la clé donnée
  2. Calculer le numéro de compartiment hash % (arrayLength-1)
  3. Obtenez une référence au premier objet Entry dans le compartiment et, au moyen de la méthode equals, parcourez toutes les entrées du compartiment donné. Finalement, nous trouverons le bon Entry. Si un élément souhaité n'est pas trouvé, retourneznull

3
C'est faux, hash % (arrayLength-1)ce serait hash % arrayLength. Mais c'est effectivement le cas hash & (arrayLength-1) . Autrement dit, car il utilise des puissances de deux ( 2^n) pour la longueur du tableau, en prenant nles bits les moins significatifs.
weston

Je pense que ce n'est pas un tableau d'objets Entity mais plutôt un tableau de LinkedList / Tree. Et chaque arbre possède en interne des objets Entity.
Mudit bhaintwal

@shevchyk pourquoi stockons-nous la clé et le hachage? quelle est leur utilité? Ne perdons-nous pas la mémoire ici?
roottraveller

hashset utilise en interne hashmap. les règles d'ajout et de suppression de hashmap sont-elles valables pour hashset?
suréchange

2
@weston non seulement cela, hashCode est un intqui peut bien sûr être négatif, faire du modulo sur un nombre négatif vous donnera un nombre négatif
Eugene

35

Vous pouvez trouver d'excellentes informations sur http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html

Résumer:

HashMap fonctionne sur le principe du hachage

put (clé, valeur): HashMap stocke à la fois la clé et l'objet de valeur en tant que Map.Entry. Hashmap applique le code de hachage (clé) pour obtenir le compartiment. en cas de collision, HashMap utilise LinkedList pour stocker l'objet.

get (key): HashMap utilise le hashcode de Key Object pour trouver l'emplacement du compartiment, puis appeler la méthode keys.equals () pour identifier le nœud correct dans LinkedList et renvoyer l'objet de valeur associé pour cette clé dans Java HashMap.


3
J'ai trouvé la réponse fournie par Jasper meilleure, je sentais que le blog était plus axé sur la gestion des entretiens que sur la compréhension du concept
Narendra N

@NarendraN Je suis d'accord avec vous.
Abhijit Gaikwad

22

Voici une description approximative du HashMapmécanisme de, pour la Java 8version, (il peut être légèrement différent de Java 6) .


Structures de données

  • Table de
    hachage La valeur de hachage est calculée via une hash()clé, et elle décide quel compartiment de la table de hachage utiliser pour une clé donnée.
  • Liste liée (individuellement)
    Lorsque le nombre d'éléments dans un compartiment est petit, une liste liée individuellement est utilisée.
  • Arbre rouge-noir
    Lorsque le nombre d'éléments dans un compartiment est important, un arbre rouge-noir est utilisé.

Cours (internes)

  • Map.Entry
    Représente une seule entité sur la carte, l'entité clé / valeur.
  • HashMap.Node
    Version de liste liée du nœud.

    Cela pourrait représenter:

    • Un seau à hachage.
      Parce qu'il a une propriété de hachage.
    • Un noeud dans la liste chaînée simple, (donc également la tête de liste chaînée) .
  • HashMap.TreeNode
    Version arborescente du nœud.

Champs (internes)

  • Node[] table
    La table bucket, (tête des listes chaînées).
    Si un compartiment ne contient pas d'éléments, il est alors nul, ne prend donc que l'espace d'une référence.
  • Set<Map.Entry> entrySet Ensemble d'entités.
  • int size
    Nombre d'entités.
  • float loadFactor
    Indiquez à quel point la table de hachage est autorisée avant de redimensionner.
  • int threshold
    La prochaine taille à laquelle redimensionner.
    Formule:threshold = capacity * loadFactor

Méthodes (internes)

  • int hash(key)
    Calculez le hachage par clé.
  • Comment mapper le hachage au compartiment?
    Utilisez la logique suivante:

    static int hashToBucket(int tableSize, int hash) {
        return (tableSize - 1) & hash;
    }

À propos de la capacité

Dans la table de hachage, la capacité signifie le nombre de seaux, il peut être obtenu table.length.
Peut également être calculé via thresholdet loadFactor, donc pas besoin d'être défini comme un champ de classe.

Pourrait obtenir la capacité effective via: capacity()


Les opérations

  • Recherchez l'entité par clé.
    Trouvez d'abord le compartiment par valeur de hachage, puis bouclez la liste chaînée ou recherchez l'arborescence triée.
  • Ajoutez une entité avec la clé.
    Trouvez d'abord le compartiment en fonction de la valeur de hachage de la clé.
    Essayez ensuite de trouver la valeur:
    • S'il est trouvé, remplacez la valeur.
    • Sinon, ajoutez un nouveau nœud au début de la liste chaînée ou insérez-le dans l'arborescence triée.
  • Redimensionner
    Une fois thresholdatteint, doublera la capacité de la table de hachage ( table.length), puis effectuera un nouveau hachage sur tous les éléments pour reconstruire la table.
    Cela pourrait être une opération coûteuse.

Performance

  • get & put
    La complexité du temps est O(1), car:
    • Le compartiment est ainsi accessible via un index de tableau O(1).
    • La liste liée dans chaque compartiment est de petite longueur, peut donc être vue comme O(1).
    • Taille arbre est également limitée, car prolongera la capacité et re-hachage lors de l' augmentation de nombre d'éléments, donc pourrait le voir comme O(1), non O(log N).

Pouvez-vous s'il vous plaît donner un exemple Comment la complexité du temps O (1)
Jitendra

@jsroyal Cela pourrait expliquer la complexité plus clairement: en.wikipedia.org/wiki/Hash_table . Mais en bref: trouver le compartiment cible est O (1), car vous le trouvez par index dans un tableau; puis dans un bucket, le montant des éléments est petit et en moyenne un nombre constant malgré le nombre total d'éléments dans la table de hachage entière, donc la recherche de l'élément cible dans un bucket est aussi O (1); ainsi, O (1) + O (1) = O (1).
Eric Wang

14

Le code de hachage détermine le compartiment que la carte de hachage doit vérifier. S'il y a plus d'un objet dans le compartiment, une recherche linéaire est effectuée pour trouver l'élément dans le compartiment égal à l'élément souhaité (à l'aide de la equals()méthode).

En d'autres termes, si vous avez un code de hachage parfait, l'accès au hashmap est constant, vous n'aurez jamais à parcourir un compartiment (techniquement, vous devrez également avoir des compartiments MAX_INT, l'implémentation Java peut partager quelques codes de hachage dans le même compartiment pour réduire les besoins en espace). Si vous avez le pire code de hachage (renvoie toujours le même numéro), votre accès à la carte de hachage devient linéaire car vous devez rechercher dans chaque élément de la carte (ils sont tous dans le même compartiment) pour obtenir ce que vous voulez.

La plupart du temps, un code de hachage bien écrit n'est pas parfait mais est suffisamment unique pour vous donner un accès plus ou moins constant.


11

Vous vous trompez sur le point trois. Deux entrées peuvent avoir le même code de hachage mais ne pas être égales. Jetez un œil à l'implémentation de HashMap.get depuis OpenJdk . Vous pouvez voir qu'il vérifie que les hachages sont égaux et que les clés sont égales. Si le point trois était vrai, il serait inutile de vérifier que les clés sont égales. Le code de hachage est comparé avant la clé car le premier est une comparaison plus efficace.

Si vous souhaitez en savoir un peu plus à ce sujet, jetez un coup d'œil à l'article de Wikipedia sur la résolution de collision Open Addressing , qui, je crois, est le mécanisme utilisé par l'implémentation OpenJdk. Ce mécanisme est subtilement différent de l'approche "bucket" mentionnée dans l'une des autres réponses.


6
import java.util.HashMap;

public class Students  {
    String name;
    int age;

    Students(String name, int age ){
        this.name = name;
        this.age=age;
    }

    @Override
    public int hashCode() {
        System.out.println("__hash__");
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("__eq__");
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Students other = (Students) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    public static void main(String[] args) {

        Students S1 = new Students("taj",22);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Output:

__ hash __

116232

__ hash __

116201

__ hash __

__ hash __

2

Nous voyons donc ici que si les objets S1 et S2 ont tous deux un contenu différent, nous sommes à peu près sûrs que notre méthode Hashcode remplacée générera un Hashcode différent (116232, 11601) pour les deux objets. MAINTENANT car il existe différents codes de hachage, il ne sera même pas la peine d'appeler la méthode EQUALS. Parce qu'un Hashcode différent GARANTIT UN CONTENU DIFFÉRENT dans un objet.

    public static void main(String[] args) {

        Students S1 = new Students("taj",21);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Now lets change out main method a little bit. Output after this change is 

__ hash __

116201

__ hash __

116201

__ hash __

__ hash __

__ eq __

1
We can clearly see that equal method is called. Here is print statement __eq__, since we have same hashcode, then content of objects MAY or MAY not be similar. So program internally  calls Equal method to verify this. 


Conclusion 
If hashcode is different , equal method will not get called. 
if hashcode is same, equal method will get called.

Thanks , hope it helps. 

3

deux objets sont égaux, implique qu'ils ont le même hashcode, mais pas vice versa.

2 objets égaux ------> ils ont le même hashcode

2 objets ont le même hashcode ---- xxxxx -> ils ne sont PAS égaux

Mise à jour Java 8 dans HashMap-

vous faites cette opération dans votre code -

myHashmap.put("old","old-value");
myHashMap.put("very-old","very-old-value");

supposons donc que votre code de hachage soit retourné pour les deux clés "old"et qu'il "very-old"soit le même. Alors que se passera-t-il.

myHashMapest un HashMap, et supposez qu'au départ vous n'avez pas spécifié sa capacité. La capacité par défaut par Java est donc de 16. Donc, dès que vous avez initialisé la table de hachage à l'aide du nouveau mot clé, elle a créé 16 compartiments. maintenant, lorsque vous avez exécuté la première instruction-

myHashmap.put("old","old-value");

alors le code de hachage pour "old"est calculé, et parce que le code de hachage peut également être un très grand entier, donc, java a fait cela en interne - (le hachage est le code de hachage ici et >>> est le décalage à droite)

hash XOR hash >>> 16

donc pour donner une image plus grande, il renverra un index, qui serait compris entre 0 et 15. Maintenant, votre paire de valeurs de clé "old"et "old-value"sera convertie en clé d'instance d'objet d'entrée et variable d'instance de valeur. puis cet objet d'entrée sera stocké dans le compartiment, ou vous pouvez dire qu'à un index particulier, cet objet d'entrée serait stocké.

FYI- Entry est une classe dans Map interface- Map.Entry, avec ces signature / définition

class Entry{
          final Key k;
          value v;
          final int hash;
          Entry next;
}

maintenant, lorsque vous exécutez la prochaine instruction -

myHashmap.put("very-old","very-old-value");

et "very-old"donne le même code de hachage que "old", donc cette nouvelle paire de valeurs clés est à nouveau envoyée au même index ou au même compartiment. Mais comme ce compartiment n'est pas vide, la nextvariable de l'objet Entry est utilisée pour stocker cette nouvelle paire de valeurs clés.

et cela sera stocké en tant que liste chaînée pour chaque objet qui a le même code de hachage, mais un TRIEFY_THRESHOLD est spécifié avec la valeur 6. donc après cela, la liste chaînée est convertie en arbre équilibré (arbre rouge-noir) avec le premier élément comme le racine.


réponse impressionnante (y)
Sudhanshu Gaur

2

Chaque objet Entry représente une paire valeur / clé. Le champ fait ensuite référence à un autre objet Entry si un compartiment a plus d'une entrée.

Parfois, il peut arriver que les codes de hachage pour 2 objets différents soient identiques. Dans ce cas, 2 objets seront enregistrés dans un compartiment et seront présentés comme LinkedList. Le point d'entrée est un objet récemment ajouté. Cet objet fait référence à un autre objet avec le champ suivant et donc un. La dernière entrée fait référence à null. Lorsque vous créez HashMap avec le constructeur par défaut

Le tableau est créé avec une taille 16 et un équilibre de charge de 0,75 par défaut.

entrez la description de l'image ici

(La source)


1

La carte de hachage fonctionne sur le principe du hachage

La méthode HashMap get (Key k) appelle la méthode hashCode sur l'objet clé et applique le hashValue retourné à sa propre fonction de hachage statique pour trouver un emplacement de compartiment (tableau de sauvegarde) où les clés et les valeurs sont stockées sous la forme d'une classe imbriquée appelée Entry (Map. Entrée). Vous avez donc conclu qu'à partir de la ligne précédente, la clé et la valeur sont stockées dans le compartiment sous la forme d'un objet Entry. Donc, penser que seule la valeur est stockée dans le seau n'est pas correct et ne donnera pas une bonne impression à l'intervieweur.

  • Chaque fois que nous appelons la méthode get (Key k) sur l'objet HashMap. Tout d'abord, il vérifie que la clé est nulle ou non. Notez qu'il ne peut y avoir qu'une seule clé nulle dans HashMap.

Si la clé est nulle, les clés nulles sont toujours mappées sur le hachage 0, donc l'index 0.

Si la clé n'est pas nulle, elle appellera la fonction de hachage sur l'objet clé, voir la ligne 4 dans la méthode ci-dessus, c'est-à-dire key.hashCode (), donc après que key.hashCode () renvoie hashValue, la ligne 4 ressemble à

            int hash = hash(hashValue)

et maintenant, il applique hashValue retourné dans sa propre fonction de hachage.

Nous pouvons nous demander pourquoi nous calculons à nouveau la valeur de hachage à l'aide de hachage (hashValue). La réponse est: il se défend contre les fonctions de hachage de mauvaise qualité.

La valeur de hachage finale est maintenant utilisée pour trouver l'emplacement du compartiment dans lequel l'objet Entry est stocké. Les objets d'entrée sont stockés dans le compartiment comme ceci (hachage, clé, valeur, bucketindex)


1

Je n'entrerai pas dans les détails du fonctionnement de HashMap, mais je donnerai un exemple afin que nous puissions nous rappeler comment HashMap fonctionne en le reliant à la réalité.

Nous avons Key, Value, HashCode et bucket.

Pendant un certain temps, nous relierons chacun d'eux aux éléments suivants:

  • Seau -> Une société
  • HashCode -> Adresse de la société (toujours unique)
  • Valeur -> Une maison dans la société
  • Clé -> Adresse de la maison.

Utilisation de Map.get (clé):

Stevie veut se rendre à la maison de son ami (Josse) qui vit dans une villa dans une société VIP, que ce soit JavaLovers Society. L'adresse de Josse est son SSN (qui est différent pour tout le monde). Il existe un index dans lequel nous découvrons le nom de la société sur la base de SSN. Cet index peut être considéré comme un algorithme pour découvrir le HashCode.

  • Nom de la société SSN
  • 92313 (Josse's) - JavaLovers
  • 13214 - AngularJSLovers
  • 98080 - JavaLovers
  • 53808 - BiologyLovers

  1. Ce SSN (clé) nous donne d'abord un HashCode (de la table d'index) qui n'est rien d'autre que le nom de la société.
  2. Maintenant, plusieurs maisons peuvent être dans la même société, donc le HashCode peut être commun.
  3. Supposons que la Société soit commune à deux maisons, comment allons-nous identifier à quelle maison nous allons, oui, en utilisant la clé (SSN) qui n'est rien d'autre que l'adresse de la maison

Utilisation de Map.put (clé, valeur)

Cela trouve une société appropriée pour cette valeur en trouvant le HashCode, puis la valeur est stockée.

J'espère que cela aide et que cela est ouvert à des modifications.


0

Ça va être une longue réponse, prenez un verre et lisez la suite…

Le hachage consiste à stocker en mémoire une paire clé-valeur qui peut être lue et écrite plus rapidement. Il stocke les clés dans un tableau et les valeurs dans une LinkedList.

Disons que je veux stocker 4 paires de valeurs clés -

{
girl => ahhan , 
misused => Manmohan Singh , 
horsemints => guess what”, 
no => way
}

Donc, pour stocker les clés, nous avons besoin d'un tableau de 4 éléments. Maintenant, comment mapper l'une de ces 4 clés à 4 index de tableau (0,1,2,3)?

Ainsi, java trouve le hashCode de clés individuelles et les mappe à un index de tableau particulier. Formules Hashcode est -

1) reverse the string.

2) keep on multiplying ascii of each character with increasing power of 31 . then add the components .

3) So hashCode() of girl would be –(ascii values of  l,r,i,g are 108, 114, 105 and 103) . 

e.g. girl =  108 * 31^0  + 114 * 31^1  + 105 * 31^2 + 103 * 31^3  = 3173020

Hash et fille !! Je sais à quoi tu penses. Votre fascination pour ce duo sauvage pourrait vous faire manquer une chose importante.

Pourquoi java le multiplie par 31?

C'est parce que 31 est un nombre premier impair sous la forme 2 ^ 5 - 1. Et un nombre impair réduit les risques de collision de hachage

Maintenant, comment ce code de hachage est mappé à un index de tableau?

réponse est Hash Code % (Array length -1) . “girl”Est donc mappé à(3173020 % 3) = 1 dans notre cas. qui est le deuxième élément du tableau.

et la valeur "ahhan" est stockée dans une LinkedList associée à l'index de tableau 1.

HashCollision - Si vous essayez de trouver hasHCodeles clés “misused”et d' “horsemints”utiliser les formules décrites ci-dessus, vous verrez les deux nous donner la même chose 1069518484. Whooaa !! leçon apprise -

2 objets égaux doivent avoir le même hashCode mais il n'y a aucune garantie si le hashCode correspond alors les objets sont égaux. Il doit donc stocker les deux valeurs correspondant à «mal utilisé» et «horsemints» au compartiment 1 (1069518484% 3).

Maintenant, la carte de hachage ressemble à -

Array Index 0 
Array Index 1 - LinkedIst (“ahhan , Manmohan Singh , guess what”)
Array Index 2  LinkedList (“way”)
Array Index 3  

Maintenant, si un corps essaie de trouver la valeur de la clé “horsemints”, java trouvera rapidement le hashCode de celui-ci, le modulera et commencera à rechercher sa valeur dans la LinkedList correspondanteindex 1 . Ainsi, nous n'avons pas besoin de rechercher tous les 4 index de tableau, ce qui accélère l'accès aux données.

Mais attendez une seconde. il y a 3 valeurs dans ce LinkedList correspondant Array index 1, comment il découvre laquelle était la valeur pour les «horsemints» clés?

En fait, j'ai menti, quand j'ai dit que HashMap stocke juste des valeurs dans LinkedList.

Il stocke les deux paires de valeurs clés comme entrée de carte. Donc, en réalité, Map ressemble à ceci.

Array Index 0 
Array Index 1 - LinkedIst (<”girl => ahhan”> , <” misused => Manmohan Singh”> , <”horsemints => guess what”>)
Array Index 2  LinkedList (<”no => way”>)
Array Index 3  

Maintenant, vous pouvez voir En parcourant la LinkedList correspondant à ArrayIndex1, il compare en fait la clé de chaque entrée à celle de cette LinkedList à «horsemints» et quand il en trouve une, il n'en retourne que la valeur.

J'espère que vous vous êtes amusé en le lisant :)


Je pense que c'est faux: "Il stocke les clés dans un tableau et les valeurs dans une LinkedList."
ACV du

chaque élément de la liste pour chaque compartiment contient la clé et la valeur ainsi que la référence au nœud suivant.
ACV du

0

Comme on dit, une image vaut 1000 mots. Je dis: un code vaut mieux que 1000 mots. Voici le code source de HashMap. Méthode Get:

/**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            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);
            }
        }
        return null;
    }

Il devient donc clair que le hachage est utilisé pour trouver le "compartiment" et le premier élément est toujours vérifié dans ce compartiment. Sinon, alorsequals la clé est utilisée pour trouver l'élément réel dans la liste chaînée.

Voyons la put()méthode:

  /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

C'est un peu plus compliqué, mais il devient clair que le nouvel élément est placé dans l'onglet à la position calculée en fonction du hachage:

i = (n - 1) & hashvoici il'index où sera placé le nouvel élément (ou c'est le "bucket"). nest la taille du tabtableau (tableau de "compartiments").

Tout d'abord, on essaie de le mettre comme premier élément de ce "seau". S'il y a déjà un élément, ajoutez un nouveau nœud à la liste.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.