web-dev-qa-db-fra.com

Implémentation HashMap en Java. Comment fonctionne le calcul d'indice de compartiment?

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

44
gnreddy

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.

29
Bohemian

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. 

2
Ramesh PVK

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 

 enter image description here

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 

0
Linh