web-dev-qa-db-fra.com

Implémentation interne de Java.util.HashMap et HashSet

J'ai essayé de comprendre l'implémentation interne de Java.util.HashMap et Java.util.HashSet.

Voici les doutes qui me traversent l'esprit pendant un moment:

  1. Quelle est l'importance de la @Override public int hashcode() dans un HashMap/HashSet? Où ce code de hachage est-il utilisé en interne?
  2. J'ai généralement vu la clé du HashMap être une String comme myMap<String,Object>. Puis-je mapper les valeurs sur someObject (au lieu de String) comme myMap<someObject, Object>? A quels contrats dois-je obéir pour que cela se passe avec succès?

Merci d'avance !

MODIFIER:

  1. Sommes-nous en train de dire que le code de hachage de la clé (check!) Est l'élément réel par rapport auquel la valeur est mappée dans la table de hachage? Et quand nous faisons myMap.get(someKey);, Java appelle en interne someKey.hashCode() pour obtenir le nombre dans la table de hachage à rechercher pour la valeur résultante?

Réponse: Oui.

EDIT 2:

  1. Dans un Java.util.HashSet, d'où provient la clé générée pour la table de hachage? Est-ce de l'objet que nous ajoutons par exemple. mySet.add(myObject); alors myObject.hashCode() va décider où cela est placé dans la table de hachage? (comme nous ne donnons pas les clés dans un HashSet).

Réponse: L'objet ajouté devient la clé. La valeur est factice!

18
peakit

La réponse à la question 2 est simple - vous pouvez utiliser n’importe quel objet de votre choix. Les cartes ayant des clés de type String sont largement utilisées car ce sont des structures de données typiques pour les services de nommage. Mais en général, vous pouvez mapper deux types quelconques comme Map<Car,Vendor> ou Map<Student,Course>.

Pour la méthode hashcode (), la réponse est la même que précédemment: chaque fois que vous substituez equals (), vous devez remplacer hashcode () pour respecter le contrat. Par contre, si vous êtes satisfait de la mise en œuvre standard d’equals (), vous ne devriez pas toucher hashcode () (car cela risquerait de rompre le contrat et d’obtenir des codes de hachage identiques pour des objets inégaux).

Note pratique: Eclipse (et probablement d'autres IDE également) peut générer automatiquement une paire d'implémentations equals () et hashcode () pour votre classe, en fonction des membres de la classe.

Modifier

Pour votre question supplémentaire: oui, exactement. Regardez le code source pour HashMap.get (Object key); il appelle key.hashcode pour calculer la position (bin) dans la table de hachage interne et renvoie la valeur à cette position (le cas échéant).

Mais soyez prudent avec les méthodes hashcode/equals 'handmade' - si vous utilisez un objet comme clé, assurez-vous que le hashcode ne change pas par la suite, sinon vous ne retrouverez plus les valeurs mappées. En d'autres termes, les champs que vous utilisez pour calculer égaux et hashcode doivent être finaux (ou "non modifiables" après la création de l'objet).

Supposons que nous avons un contact avec String name et String phonenumber et que nous utilisons les deux champs pour calculer equals () et hashcode (). Maintenant, nous créons "John Doe" avec son numéro de téléphone portable et le mappons vers son magasin de beignets préféré. hashcode () est utilisé pour calculer l'index (bin) dans la table de hachage et c'est là que le magasin de beignets est stocké. 

Nous apprenons maintenant qu'il a un nouveau numéro de téléphone et nous changeons le champ de numéro de téléphone de l'objet John Doe. Cela se traduit par un nouveau hashcode. Et ce code de hachage se résout en un nouvel index de table de hachage - qui n'est généralement pas l'emplacement dans lequel le magasin Donut préféré de John Does a été stocké.

Le problème est clair: dans ce cas, nous voulions associer "John Doe" au magasin Donut, et non "John Doe avec un numéro de téléphone spécifique". Donc, nous devons faire attention avec les égaux/hashcode générés automatiquement pour nous assurer qu'ils correspondent à ce que nous voulons vraiment, car ils pourraient utiliser des champs non désirés, ce qui créerait des problèmes avec HashMaps et HashSets.

Modifier 2

Si vous ajoutez un objet à un hachage, l'objet est la clé de la table de hachage interne, la valeur est définie mais non utilisée (juste une instance statique de l'objet). Voici l'implémentation de l'openjdk 6 (b17):

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;

public boolean add(E e) {
  return map.put(e, PRESENT)==null;
}
14
Andreas_D

Les conteneurs de hachage tels que HashMap et HashSet fournissent un accès rapide aux éléments qui y sont stockés en divisant leur contenu en "compartiments".

Par exemple, la liste de nombres: 1, 2, 3, 4, 5, 6, 7, 8 stockée dans une List ressemblerait (conceptuellement) en mémoire à quelque chose comme: [1, 2, 3, 4, 5, 6, 7, 8].

Stocker le même ensemble de nombres dans une Set ressemblerait davantage à ceci: [1, 2] [3, 4] [5, 6] [7, 8]. Dans cet exemple, la liste a été divisée en 4 compartiments. 

Maintenant, imaginez que vous souhaitiez trouver la valeur 6 parmi List et Set. Avec une liste, vous devez commencer au début de la liste et vérifier chaque valeur jusqu'à ce que vous obteniez 6, cela prend 6 étapes. Avec un ensemble, vous trouvez le bon compartiment, cochez chacun des éléments de ce panier (seulement 2 dans notre exemple), ce qui en fait un processus en 3 étapes. La valeur de cette approche augmente considérablement avec le nombre de données dont vous disposez.

Mais attendez, comment avons-nous su dans quel seau regarder? C'est là qu'intervient la méthode hashCode. Pour déterminer le compartiment dans lequel rechercher un élément, appelez les conteneurs de hachage Java hashCode, puis appliquez une fonction au résultat. Cette fonction essaie d’équilibrer le nombre de compartiments et le nombre d’éléments pour une recherche la plus rapide possible.

Lors de la recherche, une fois que le compartiment correct a été trouvé, chaque élément de ce compartiment est comparé un par un, comme dans une liste. C'est pourquoi, lorsque vous remplacez hashCode, vous devez également remplacer equals. Ainsi, si un objet de n'importe quel type a à la fois une méthode equals et une méthode hashCode, il peut être utilisé comme clé dans une Map ou une entrée dans une Set. Il existe un contrat à suivre pour implémenter correctement ces méthodes. Le texte canonique est tiré du grand livre de Josh Bloch, Effective Java, Effective Java: Elément 8: Toujours écraser hashCode lorsque vous écrasez égal à

5
Tendayi Mawushe

Quelle est l’importance de @Override public int hashcode () dans un HashMap/HashSet?

Cela permet à l'instance de la carte de produire un code de hachage utile en fonction du contenu de la carte. Deux cartes avec le même contenu produiront le même code de hachage. Si le contenu est différent, le code de hachage sera différent.

Où ce code de hachage est-il utilisé en interne?

Jamais. Ce code n'existe que pour que vous puissiez utiliser une carte comme clé dans une autre carte.

Puis-je mapper les valeurs sur someObject (au lieu de String) comme myMap<someObject, Object>?

Oui, mais someObject doit être une classe et non un objet (votre nom suggère que vous souhaitiez transmettre un objet; il devrait être SomeObject pour indiquer clairement que vous faites référence au type).

A quels contrats dois-je obéir pour que cela se passe avec succès?

La classe doit implémenter hashCode() et equals().

[MODIFIER]

Sommes-nous en train de dire que le code de hachage de la clé (check!) Est la chose réelle par rapport à laquelle la valeur est mappée dans la table de hachage?

Oui.

5
Aaron Digulla

Oui. Vous pouvez utiliser n'importe quel objet comme clé dans un HashMap. Pour ce faire, suivez les étapes à suivre. 

  1. Remplacer égale. 

  2. Remplacez hashCode. 

Les contrats pour les deux méthodes sont très clairement mentionnés dans la documentation de Java.lang.Object. http://Java.Sun.com/javase/6/docs/api/Java/lang/Object.html

Et oui, la méthode hashCode () est utilisée en interne par HashMap et, par conséquent, il est important de renvoyer une valeur correcte pour les performances. 

Voici la méthode hashCode () de HashMap 

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    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;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

Il ressort clairement du code ci-dessus que hashCode de chaque clé n'est pas uniquement utilisé pour hashCode () de la carte, mais également pour rechercher le compartiment dans lequel placer la clé, paire de valeurs. C'est pourquoi hashCode () est lié aux performances de HashMap.

5
Varun
  1. Toute Object en Java doit avoir une méthode hashCode(); HashMap et HashSet ne sont pas des exceptions. Ce code de hachage est utilisé si vous insérez la carte de hachage/ensemble dans une autre carte de hachage/ensemble.
  2. Tout type de classe peut être utilisé comme clé dans une variable HashMap/HashSet. Cela nécessite que la méthode hashCode() renvoie des valeurs égales pour des objets identiques et que la méthode equals() soit implémentée conformément au contrat (réflexif, transitif, symétrique). Les implémentations par défaut de Object obéissent déjà à ces contrats, mais vous pouvez les remplacer si vous voulez l'égalité de valeur au lieu d'une égalité de référence.
3
Thomas

Aaron Digulla a absolument raison. Une remarque supplémentaire intéressante que les gens ne semblent pas comprendre est que la méthode hashCode () de l'objet clé n'est pas utilisée telle quelle. En fait, il est réorganisé par HashMap, c’est-à-dire qu’il appelle hash(someKey.hashCode)), où hash() est une méthode de hachage interne.

Pour voir cela, regardez la source: http://kickjava.com/src/Java/util/HashMap.Java.htm

La raison en est que certaines personnes implémentent mal hashCode () et que la fonction hash () donne une meilleure distribution de hachage. C'est essentiellement fait pour des raisons de performance.

2
GaryF

En réponse à la question 2, bien que vous puissiez avoir n'importe quelle classe pouvant être utilisée comme clé dans Hashmap, la meilleure pratique consiste à utiliser des classes immuables comme clés pour HashMap. Ou au moins si votre implémentation "hashCode" et "equals" dépend de certains attributs de votre classe, veillez à ne pas fournir de méthodes pour modifier ces attributs.

2
sateesh

Il existe une relation complexe entre equals (), hashcode() et les tables de hachage en général en Java (et .NET également). Pour citer la documentation:

public int hashCode()

Retourne une valeur de code de hachage pour l'objet. Cette méthode est prise en charge au profit des tables de hachage telles que celles fournies par Java.util.Hashtable.

Le contrat général de hashCode est: 

  • Chaque fois qu'elle est appelée plusieurs fois sur le même objet lors de l'exécution d'une application Java, la méthode hashCode doit systématiquement renvoyer le même entier, à condition qu'aucune information utilisée dans les comparaisons égales de l'objet ne soit modifiée. Cet entier n'a pas besoin de rester cohérent d'une exécution d'une application à une autre exécution de la même application. 
  • Si deux objets sont égaux selon la méthode equals (Object), l'appel de la méthode hashCode sur chacun des deux objets doit produire le même résultat entier. 
  • Il n'est pas nécessaire que si deux objets ne soient pas égaux selon la méthode equals (Java.lang.Object), l'appel de la méthode hashCode sur chacun des deux objets doit produire des résultats entiers distincts. Cependant, le programmeur doit savoir que la production de résultats entiers distincts pour des objets inégaux peut améliorer les performances des hashtables.

Dans la mesure du possible, la méthode hashCode définie par la classe Object renvoie des entiers distincts pour des objets distincts. (Ceci est généralement implémenté en convertissant l'adresse interne de l'objet en un entier, mais cette technique d'implémentation n'est pas requise par le langage de programmation Java ™.) 

La ligne

@Overrides public int hashCode()

indique simplement que la méthode hashCode() est remplacée. Ceci ia habituellement un signe qu'il est prudent d'utiliser le type comme clé dans un HashMap.

Et oui, vous pouvez utiliser n'importe quel objet qui obéit au contrat pour equals() et hashCode() dans une HashMap comme clé.

2
Joey

Méthode HashCode pour les classes de collection telles que HashSet, HashTable, HashMap, etc. - Le code de hachage renvoie un nombre entier pour l'objet pris en charge aux fins du hachage. Il est implémenté en convertissant l'adresse interne de l'objet en un entier. La méthode du code de hachage doit être remplacée dans chaque classe qui substitue la méthode égal à. Trois contacts généraux pour la méthode HashCode

  • Pour deux objets égaux acc. pour être égal à la méthode, puis en appelant HashCode pour les deux objets, la même valeur entière devrait être produite.

  • S'il est appelé plusieurs fois pour un seul objet, il doit alors renvoyer une valeur entière constante.

  • Pour deux objets inégaux selon. à une méthode égale, puis en appelant la méthode HashCode pour les deux objets, il n'est pas obligatoire qu'il produise une valeur distincte.

0
LearnerJava