Selon ma compréhension, je pense:
Ai-je raison?
Maintenant, si je suis correct, j'ai la question suivante: La HashMap
utilise en interne le hashcode de l'objet. Donc, si deux objets peuvent avoir le même hashcode, comment la HashMap
peut-elle suivre quelle clé elle utilise?
Quelqu'un peut-il expliquer comment la variable HashMap
utilise en interne le hashcode de l'objet?
Un hashmap fonctionne comme ceci (ceci est un peu simplifié, mais il illustre le mécanisme de base):
Il utilise un certain nombre de "compartiments" pour stocker les paires clé-valeur. Chaque compartiment a un numéro unique - c'est ce qui l'identifie. Lorsque vous insérez 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'identificateur 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 compartiment numéro 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 attribuant une clé, il 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 à equals()
.
Vous pouvez maintenant voir à quel point cela est très efficace pour rechercher des paires clé-valeur dans une carte: grâce au code de hachage de la clé, la table de hachage sait immédiatement dans quel compartiment il doit regarder, de sorte qu'il ne doit se confronter qu'à ce qu'il contient.
En examinant le mécanisme ci-dessus, vous pouvez également voir quelles conditions sont nécessaires pour les méthodes de touches hashCode()
et equals()
:
Si deux clés sont identiques (equals()
renvoie true
lorsque vous les comparez), leur méthode hashCode()
doit renvoyer le même nombre. Si les clés violent cela, les clés égales peuvent être stockées dans des compartiments différents et la table de hachage ne pourra pas trouver les paires clé-valeur (car elle va rechercher dans le même compartiment).
Si deux clés sont différentes, peu importe si leur code de hachage est identique ou non. Ils seront stockés dans le même compartiment si leurs codes de hachage sont identiques, et dans ce cas, la table de hachage utilisera equals()
pour les différencier.
Votre troisième affirmation est incorrecte.
Il est parfaitement légal que deux objets inégaux aient le même code de hachage. HashMap
l'utilise comme "filtre de premier passage" afin que la carte puisse trouver rapidement les entrées possible avec la clé spécifiée. Les clés avec le même code de hachage sont ensuite testées pour vérifier 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 à 232 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.)
HashMap
est un tableau d'objets Entry
.
Considérez HashMap
comme un simple tableau d'objets.
Jetez un coup d'œil à ce qu'est cette Object
:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
…
}
Chaque objet Entry
représente une paire clé-valeur. Le champ next
fait référence à un autre objet Entry
si un compartiment a plusieurs Entry
.
Parfois, il peut arriver que les codes de hachage de 2 objets différents soient identiques. Dans ce cas, deux objets seront enregistrés dans un compartiment et présentés sous forme de liste chaînée. Le point d’entrée est l’objet ajouté le plus récemment. Cet objet fait référence à un autre objet avec le champ next
et ainsi de suite. La dernière entrée fait référence à null
.
Lorsque vous créez une HashMap
avec le constructeur par défaut
HashMap hashMap = new HashMap();
Le tableau est créé avec une taille de charge de 16 et un solde de charge de 0,75.
hash % (arrayLength-1)
où l’élément doit être placé (numéro de compartiment)HashMap
, la valeur est écrasée.Si le compartiment contient déjà au moins un élément, un nouvel élément est ajouté et placé à la première position du compartiment. Son champ next
fait référence à l'ancien élément.
hash % (arrayLength-1)
Entry
. Si un élément souhaité n’est pas trouvé, retourne null
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 de hachage
put (clé, valeur): HashMap stocke la clé et l'objet valeur sous la forme Map.Entry. Hashmap applique le hashcode (clé) pour obtenir le compartiment. en cas de collision, HashMap utilise LinkedList pour stocker un objet.
get (key): HashMap utilise le hashcode de Key Object pour rechercher l'emplacement du compartiment, puis appelle la méthode keys.equals () pour identifier le noeud correct dans LinkedList et renvoie l'objet de valeur associé à cette clé dans Java HashMap.
Le hashcode détermine le compartiment à vérifier par la hashmap. S'il y a plus d'un objet dans le compartiment, une recherche linéaire est effectuée pour déterminer quel élément du compartiment est égal à la méthode souhaitée (à l'aide de la méthode equals()
).
En d’autres termes, si vous avez un hashcode parfait, alors que l’accès à hashmap est constant, vous n’aurez plus à itérer dans un compartiment (techniquement, vous devez également disposer de compartiments MAX_INT, l’implémentation Java peut partager plusieurs codes de hachage dans le même compartiment). réduire les besoins en espace). Si vous avez le pire hashcode (retourne toujours le même nombre), votre accès à la hashmap devient linéaire puisque 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 hashcode bien écrit n'est pas parfait mais suffisamment unique pour vous donner un accès plus ou moins constant.
Vous vous trompez sur le point trois. Deux entrées peuvent avoir le même code de hachage mais ne pas être égales. Regardez l'implémentation de HashMap.get depuis OpenJdk . Vous pouvez voir qu'il vérifie que les hachages sont égaux et les clés sont égales. Si le point trois était vrai, il serait alors 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, consultez l'article de Wikipedia sur Résolution de collision Open Addressing , qui, selon moi, est le mécanisme utilisé par la mise en oeuvre d'OpenJdk. Ce mécanisme est légèrement différent de l’approche dite du "seau", comme l’indique une autre réponse.
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 un contenu différent, nous sommes pratiquement certains que notre méthode Hashcode surchargée générera un Hashcode différent (116232,11601) pour les deux objets. NOW puisqu'il existe différents codes de hachage, il ne sera même pas utile d'appeler la méthode EQUALS. Parce qu'un code de hachage 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.
Chaque objet Entry représente une paire clé-valeur. Le champ suivant fait référence à un autre objet Entrée si un compartiment a plus d'une entrée.
Parfois, il peut arriver que hashCodes pour 2 objets différents soient identiques. Dans ce cas, 2 objets seront enregistrés dans un compartiment et seront présentés sous forme de liste liée. Le point d'entrée est un objet ajouté plus récemment. Cet objet fait référence à un autre objet avec le champ suivant et ainsi de suite. 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.
La carte de hachage fonctionne sur le principe de hachage
HashMap get (Key k), la méthode appelle la méthode hashCode sur l'objet key et applique hashValue renvoyée à sa propre fonction de hachage statique pour rechercher un emplacement de compartiment (tableau de sauvegarde) dans lequel 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 que, à partir de la ligne précédente, la clé et la valeur sont stockées dans le compartiment sous forme d'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.
Si key est null, les clés Null sont toujours mappées sur le hachage 0, donc l'index 0.
Si key n'est pas nul, il appellera hashfunction sur l'objet key, voir la ligne 4 de la méthode ci-dessus, key.hashCode (). Ainsi, après que key.hashCode () retourne hashValue, la ligne 4 ressemble à
int hash = hash(hashValue)
et maintenant, il retourne hashValue renvoyé dans sa propre fonction de hachage.
On peut se demander pourquoi nous calculons encore la valeur de hachage en utilisant hash (hashValue). La réponse est qu'il défend contre les fonctions de hachage de mauvaise qualité.
Désormais, la valeur de hachage finale est utilisée pour trouver l'emplacement du compartiment dans lequel l'objet Entrée est stocké. Les objets d’entrée sont stockés dans le compartiment comme ceci (hachage, clé, valeur, bucketindex)
Je n'entrerai pas dans les détails du fonctionnement de HashMap, mais donnerai un exemple afin que nous puissions nous rappeler comment fonctionne HashMap en le rapportant à la réalité.
Nous avons Key, Value, HashCode et un seau.
Pendant un certain temps, nous allons relier chacune d’elles avec ce qui suit:
Utilisation de Map.get (clé):
Stevie veut se rendre chez son ami (Josse) qui habite une villa dans une société VIP, qu’il s’agisse de la société JavaLovers. L'adresse de Josse est son SSN (qui est différent pour tout le monde) . Il existe un index dans lequel nous trouvons le nom de la société basé sur le SSN . Cet index peut être considéré comme un algorithme le HashCode.
Utilisation de Map.put (clé, valeur)
Ceci 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 cela est ouvert pour des modifications.
deux objets sont égaux, implique qu'ils ont le même hashcode, mais pas l'inverse
Mise à jour de Java 8 dans HashMap -
vous faites cette opération dans votre code -
myHashmap.put("old","key-value-pair");
myHashMap.put("very-old","old-key-value-pair");
alors supposons que votre hashcode retourné pour les deux clés "old"
et "very-old"
soit identique. Alors qu'est-ce qui va arriver.
myHashMap
est un HashMap, et supposons qu'au départ vous ne spécifiez pas sa capacité. La capacité par défaut selon Java est donc de 16. Ainsi, dès que vous avez initialisé hashmap avec le nouveau mot-clé, 16 compartiments ont été créés. maintenant, quand vous avez exécuté la première déclaration-
myHashmap.put("old","key-value-pair");
alors le hashcode pour "old"
est calculé, et comme le hashcode peut aussi être un très grand entier, Java le fait en interne - (le hash est hashcode ici et >>> est à droite)
hash XOR hash >>> 16
alors, pour donner une image plus grande, il retournera un index, qui serait compris entre 0 et 15. Maintenant, votre paire valeur-clé "old"
et "key-value-pair"
sera convertie en variable d'instance clé et valeur de l'objet Entry. et ensuite cet objet d'entrée sera stocké dans le compartiment, ou vous pouvez dire que, à un index particulier, cet objet d'entrée sera 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, quand vous exécuterez la prochaine déclaration -
myHashmap.put("very-old","old-key-value-pair");
et "very-old"
donne le même hashcode que "old"
, de sorte que cette nouvelle paire clé-valeur est à nouveau envoyée au même index ou au même compartiment. Mais comme ce compartiment n'est pas vide, la variable next
de l'objet Entry est utilisée pour stocker cette nouvelle paire de valeurs de clé.
et cela sera stocké en tant que liste chaînée pour chaque objet ayant le même hashcode, mais un TRIEFY_THRESHOLD est spécifié avec la valeur 6. Ainsi, après cela, la liste chaînée est convertie en arbre équilibré (arbre rouge-noir) avec le premier élément en tant que racine.
Comme on dit, une image vaut 1000 mots. Je dis: un code vaut mieux que 1000 mots. Voici le code source de HashMap. Obtenir la méthode:
/**
* 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 que le premier élément est toujours vérifié dans ce compartiment. Sinon, equals
de la clé est utilisé pour trouver l'élément réel dans la liste liée.
Voyons la méthode put()
:
/**
* 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) & hash
here i
est l'index où le nouvel élément sera placé (ou il s'agit du "compartiment"). n
est la taille du tableau tab
(tableau de "compartiments").
Premièrement, on essaie de le placer comme premier élément de ce "seau". S'il existe déjà un élément, ajoutez un nouveau nœud à la liste.