HashMap
contient un certain nombre de seaux. Il utilise hashCode
pour déterminer dans quel seau les placer. Par souci de simplicité, imaginez-le comme un module.
Si notre hashcode est 123456 et que nous avons 4 buckets, 123456 % 4 = 0
l'élément va donc dans le premier bucket, Bucket 1.
Si notre fonction de hashcode est bonne, elle devrait fournir une distribution uniforme afin que tous les compartiments soient utilisés de manière quelque peu égale. Dans ce cas, le compartiment utilise une liste liée pour stocker les valeurs.
Mais vous ne pouvez pas compter sur les gens pour implémenter de bonnes fonctions de hachage. Les gens écriront souvent des fonctions de hachage médiocres, ce qui entraînera une distribution non uniforme. Il est également possible que nous puissions simplement être malchanceux avec nos contributions.
Moins cette distribution est uniforme, plus nous nous éloignons des opérations O (1) et plus nous nous rapprochons des opérations O (n).
L'implémentation de Hashmap tente d'atténuer cela en organisant certains buckets en arborescences plutôt qu'en listes liées si les buckets deviennent trop volumineux. C'est à ça que ça TREEIFY_THRESHOLD = 8
sert. Si un seau contient plus de huit éléments, il doit devenir un arbre.
Cet arbre est un arbre rouge-noir. Il est d'abord trié par code de hachage. Si les codes de hachage sont identiques, il utilise la compareTo
méthode de Comparable
si les objets implémentent cette interface, sinon le code de hachage d'identité.
Si des entrées sont supprimées de la carte, le nombre d'entrées dans le compartiment peut être réduit de sorte que cette arborescence n'est plus nécessaire. C'est à cela que UNTREEIFY_THRESHOLD = 6
sert. Si le nombre d'éléments dans un bucket tombe en dessous de six, nous pourrions aussi bien revenir à l'utilisation d'une liste chaînée.
Enfin, il y a le MIN_TREEIFY_CAPACITY = 64
.
Lorsqu'une carte de hachage augmente en taille, elle se redimensionne automatiquement pour avoir plus de compartiments. Si nous avons une petite carte de hachage, la probabilité que nous obtenions des seaux très pleins est assez élevée, car nous n'avons pas autant de seaux différents dans lesquels placer des éléments. Il est bien préférable d'avoir une carte de hachage plus grande, avec plus de seaux moins pleins. Cette constante dit essentiellement de ne pas commencer à transformer des seaux en arbres si notre carte de hachage est très petite - elle doit d'abord être redimensionnée pour être plus grande.
Pour répondre à votre question sur le gain de performance, ces optimisations ont été ajoutées pour améliorer le pire des cas. Je ne fais que spéculer mais vous ne verriez probablement une amélioration notable des performances à cause de ces optimisations que si votre hashCode
fonction n'était pas très bonne.
String
, ont un espace de valeur bien plus grand que leint
hashcode, par conséquent, les collisions sont inévitables. Maintenant, cela dépend des valeurs réelles, comme lesString
s réels , que vous mettez dans la carte, que vous obteniez une distribution uniforme ou non. Une mauvaise distribution peut être le résultat de la malchance.java.lang.String
a un caractère déterministe et non cryptographiquehashCode
, de sorte que les attaquants peuvent créer de manière triviale des chaînes distinctes avec des hashCodes en collision. Avant cette optimisation, cela pouvait dégrader les opérations HashMap en temps O (n), maintenant il les dégrade simplement en O (log (n)).if the objects implement that interface, else the identity hash code.
je cherchais cette autre partie.MIN_TREEIFY_CAPACITY
. Cela signifie-t-il "Une fois que nous insérons une clé qui doit être hachée dans le compartiment contenant déjà 8TREEIFY_THRESHOLD
clés ( ) et s'il y a déjà 64MIN_TREEIFY_CAPACITY
clés ( )HashMap
, la liste liée de ce compartiment est convertie en arbre équilibré."Pour le dire plus simple (autant que je pourrais plus simple) + quelques détails supplémentaires.
Ces propriétés dépendent de beaucoup de choses internes qu'il serait très cool de comprendre - avant de passer directement à elles.
TREEIFY_THRESHOLD -> lorsqu'un seul seau atteint cela (et que le nombre total dépasse
MIN_TREEIFY_CAPACITY
), il se transforme en un nœud d'arbre rouge / noir parfaitement équilibré . Pourquoi? En raison de la vitesse de recherche. Pensez-y d'une manière différente:Quelques intro pour le sujet suivant. Pourquoi le nombre de bacs / seaux est-il toujours une puissance de deux ? Au moins deux raisons: plus rapide que le fonctionnement modulo et modulo sur les nombres négatifs sera négatif. Et vous ne pouvez pas placer une entrée dans un bucket "négatif":
Au lieu de cela, il y a une belle astuce utilisée à la place de modulo:
C'est sémantiquement identique au fonctionnement modulo. Il conservera les bits inférieurs. Cela a une conséquence intéressante lorsque vous faites:
C'est là que la multiplication des seaux entre en jeu. Dans certaines conditions (cela prendrait beaucoup de temps à expliquer avec précision ), les seaux sont doublés de taille. Pourquoi? Lorsque la taille des godets est doublée, un autre élément entre en jeu .
En tant que tel, ce processus est appelé re-hachage. Cela pourrait devenir lent. C'est (pour les gens qui se soucient) que HashMap est "plaisanté" comme: rapide, rapide, rapide, lent . Il existe d'autres implémentations - recherche hashmap sans pause ...
Désormais, UNTREEIFY_THRESHOLD entre en jeu après un nouveau hachage. À ce stade, certaines entrées peuvent passer de ce bac à d'autres (elles ajoutent un bit de plus au
(n-1)&hash
calcul - et en tant que tel peuvent se déplacer vers d' autres buckets) et cela peut atteindre celaUNTREEIFY_THRESHOLD
. À ce stade, il n'est pas rentable de conserver le bac sous forme dered-black tree node
, maisLinkedList
plutôt commeMIN_TREEIFY_CAPACITY est le nombre minimum de compartiments avant qu'un certain compartiment ne soit transformé en arbre.
la source
TreeNode
est une autre façon de stocker les entrées qui appartiennent à une seule case du fichierHashMap
. Dans les implémentations plus anciennes, les entrées d'un bac étaient stockées dans une liste chaînée. Dans Java 8, si le nombre d'entrées dans un bac a dépassé un seuil (TREEIFY_THRESHOLD
), elles sont stockées dans une structure arborescente au lieu de la liste chaînée d'origine. Ceci est une optimisation.De la mise en œuvre:
la source
TREEIFY_THRESHOLD
ET le nombre total de bacs est au moinsMIN_TREEIFY_CAPACITY
. J'ai essayé de couvrir cela dans ma réponse ...Vous auriez besoin de le visualiser: disons qu'il existe une clé de classe avec uniquement la fonction hashCode () remplacée pour toujours renvoyer la même valeur
puis ailleurs, j'insère 9 entrées dans un HashMap avec toutes les clés étant des instances de cette classe. par exemple
Le parcours de l'arbre est plus rapide {O (log n)} que LinkedList {O (n)} et à mesure que n grandit, la différence devient plus significative.
la source
compareTo
fromComparable
.identityHashCode
est un autre mécanisme qu'il utilise.Key
ne met pas en œuvreComparable
,identityHashCode
sera utilisé :)Le changement dans l'implémentation de HashMap a été ajouté avec JEP-180 . Le but était de:
Cependant, la performance pure n'est pas le seul gain. Cela empêchera également les attaques HashDoS , au cas où une carte de hachage serait utilisée pour stocker les entrées de l'utilisateur, car l' arbre rouge-noir utilisé pour stocker les données dans le compartiment a le pire cas de complexité d'insertion en O (log n). L'arbre est utilisé après qu'un certain critère est satisfait - voir la réponse d'Eugene .
la source
Pour comprendre l'implémentation interne de hashmap, vous devez comprendre le hachage. Le hachage dans sa forme la plus simple est un moyen d'attribuer un code unique à n'importe quelle variable / objet après avoir appliqué n'importe quelle formule / algorithme sur ses propriétés.
Une vraie fonction de hachage doit suivre cette règle -
«La fonction de hachage doit renvoyer le même code de hachage à chaque fois que la fonction est appliquée sur des objets identiques ou égaux. En d'autres termes, deux objets égaux doivent produire le même code de hachage de manière cohérente. »
la source