Je regarde l'implémentation de HashMap
en Java et je suis bloqué à un moment donné.
Comment la fonction indexFor
est-elle calculée?
static int indexFor(int h, int length) {
return h & (length-1);
}
Merci
Ce n'est pas calculer le hash , c'est calculer le seau .
L'expression h & (length-1)
effectue une variable AND
sur h
à l'aide de length-1
, qui ressemble à un masque de bits, pour ne renvoyer que les bits de poids faible de h
, créant ainsi une variante ultra-rapide de h % length
.
Il calcule le seau de la carte de hachage où l'entrée (paire clé-valeur) sera stockée. L'identifiant du compartiment est hashvalue/buckets length
.
Une carte de hachage se compose de seaux; les objets seront placés dans ces compartiments en fonction de l'identifiant du compartiment.
N'importe quel nombre d'objets peut effectivement tomber dans le même compartiment en fonction de leur valeur hash code / buckets length
. Ceci s'appelle une "collision".
Si de nombreux objets tombent dans le même seau, la recherche de leur méthode equals () sera appelée pour lever l’ambiguïté.
Le nombre de collisions est indirectement proportionnel à la longueur du godet.
La réponse ci-dessus est très bonne mais je veux expliquer davantage pourquoi Java peut utiliser indexFor
pour créer un index
Exemple, j'ai une HashMap
comme celle-ci (ce test concerne Java7, je vois que Java8 change beaucoup HashMap mais je pense que cette logique reste très bonne)
// Default length of "budget" (table.length) after create is 16 (HashMap#DEFAULT_INITIAL_CAPACITY)
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("A",1); // hash("A")=69, indexFor(hash,table.length)=69&(16-1) = 5
hashMap.put("B",2); // hash("B")=70, indexFor(hash,table.length)=70&(16-1) = 6
hashMap.put("P",3); // hash("P")=85, indexFor(hash,table.length)=85&(16-1) = 5
hashMap.put("A",4); // hash("A")=69, indexFor(hash,table.length)=69&(16-1) = 5
hashMap.put("r", 4);// hash("r")=117, indexFor(hash,table.length)=117&(16-1) = 5
Vous pouvez voir l'index d'entrée avec la clé "A"
et l'objet avec la clé "P"
et l'objet avec la clé "r"
ont le même index (= 5). Et voici le résultat du débogage après l'exécution du code ci-dessus
Le tableau dans l'image est ici
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
transient HashMap.Entry<K, V>[] table;
...
}
=> Je vois
Si index sont différents, la nouvelle entrée sera ajoutée à la table
Si index est même et hash
est même, la nouvelle valeur sera mise à jour
Si index est même et hash
est différent, la nouvelle entrée désignera une ancienne entrée (comme une LinkedList
). Alors vous savez pourquoi Map.Entry
ont le champ next
static class Entry<K, V> implements Java.util.Map.Entry<K, V> {
...
HashMap.Entry<K, V> next;
}
Vous pouvez le vérifier à nouveau en lisant le code dans HashMap
.
Comme maintenant, vous pouvez penser que HashMap
sera jamais besoin de changer la taille (16) car indexFor()
retournera toujours la valeur <= 15 mais il n'est pas correct.
Si vous regardez le code HashMap
if (this.size >= this.threshold ...) {
this.resize(2 * this.table.length);
HashMap
redimensionnera la table (longueur de la table double) lorsque size
> = threadhold
Qu'est-ce que threadhold
? threadhold
est calculé ci-dessous
static final int DEFAULT_INITIAL_CAPACITY = 16;
static final float DEFAULT_LOAD_FACTOR = 0.75F;
...
this.threshold = (int)Math.min((float)capacity * this.loadFactor, 1.07374182E9F); // if capacity(table.length) = 16 => threadhold = 12
Quelle est la size
? size
est calculé ci-dessous.
Bien sûr, size
ici n'est pastable.length
.
Chaque fois que vous mettez une nouvelle entrée dans HashMap
et HashMap
, vous devez créer une nouvelle entrée (notez que HashMap
ne crée pas de nouvelle entrée lorsque la clé est identique, elle remplace simplement la nouvelle valeur pour une entrée existante), puis size++
void createEntry(int hash, K key, V value, int bucketIndex) {
...
++this.size;
}
J'espère que ça aide