Or trois argent quatre aide interview-facile à comprendre le code source HashMap

Préface

HashMapPour chaque étude, les Javagens qui sont familiers ne peuvent pas être familiers, mais c'est une chose tellement familière, creuser vraiment dans le niveau du code source, il y a de nombreuses parties de la valeur de l'apprentissage et de la réflexion, travaillons maintenant ensemble pour explorer HashMaple code source.

Analyse du code source HashMap

HashMapSur la base d'une table de hachage, et implémente Mapune interface dans laquelle la key-valuestructure de données de paire clé-valeur y est stockée keyet dont valueles nullvaleurs sont autorisées HashMap, l'ordre n'est pas garanti, ni le fil de sécurité.

Stockage de données dans HashMap

Dans HashMap, chaque putopération sera sur le keyhachage, en fonction des résultats puis de l'index haché obtenu par le calcul de positionnement dans une plage spécifiée, cet index est la keyvaleur actuelle de l'emplacement de stockage, puisqu'il est haché, alors il y aura certainement une collision de hachage , qui dans jdk1.7les versions précédentes et chaque index est stocké dans une liste chaînée, et la jdk1.8version et au-delà, alors cela est optimisé lorsque la longueur de la chaîne est plus longue que 8le temps, sera un arbre rouge-noir en mémoire, lorsque les éléments d'arbre rouge-noir à partir d'une 8diminution supérieure ou égale ou inférieure 6, la dégradation sera à nouveau stockée sous forme de liste chaînée à partir de l'arbre rouge-noir.

La figure est jdk1.8une HashMapvue schématique de la structure de stockage (chacune appelée position cible ):

Insérez la description de l'image ici

Dans HashMaple stockage des données est réalisé par un tableau d'arbre rouge-noir + + liste ( jdk1.8), et dans les versions antérieures du tableau en utilisant uniquement + liste liée à stocker.

Pourquoi est-il recommandé de spécifier la taille lors de l'initialisation du HashMap

HashMapInitialisation, nous recommandons généralement une estimation de la taille possible, puis de construire HashMapla capacité spécifiée lorsque l'objet, c'est pourquoi? Pour répondre à cette question, voyons HashMapcomment initialiser.

Lorsque nous ne spécifions aucun paramètre pour créer la figure est HashMapsoumise:

Insérez la description de l'image ici

Facteur de charge

Vous pouvez voir qu'il existe une valeur par défaut DEFAULT_LOAD_FACTOR(facteur de charge), et cette valeur est par défaut 0.75. Lorsque l'effet du facteur de charge est HashMaputilisé dans la taille de la capacité de la capacité totale 0.75, l'expansion sera effectuée automatiquement. Ensuite, vous pouvez voir à partir du code ci-dessus, lorsque nous ne spécifions pas la taille, HashMapet n'initialisons pas une capacité, alors nous pouvons nous aventurer à deviner quand nous appelons putla méthode actuelle sera certainement jugée HashMapsi initialisée, sinon initialisée, elle le sera Initialisez d'abord.

Capacité par défaut de HashMap

putLa méthode détermine le courant HashMapsi est initialisé, il n'est pas initialisé, il appellera la resizeméthode est initialisé, resizela méthode utilisée pour initialiser non seulement, mais aussi pour l'expansion, qui est la partie d'initialisation de la partie principale de la boîte rouge de la figure:

Insérez la description de l'image ici

On peut voir, le HashMaptemps d' initialisation , principalement des 2variables initialisées : une newCapreprésentant le courant HashMapen nombre de barils, un indice du tableau représente un seau; on est newThrprincipalement utilisé pour indiquer le seuil d'expansion, car à tout moment il est impossible d'attendre jusqu'à les compartiments sont tous utilisés pour l'expansion. Par conséquent, un seuil doit être défini. Une fois le seuil atteint, l'expansion commence. Le seuil est la capacité totale multipliée par le facteur de charge.

Ci-dessus, nous savons que le facteur de charge par défaut 0.75, et la taille par défaut du seau 16, c'est donc à ce moment-là que nous initialisons le HashMaptemps sans spécifier la taille du seau lorsqu'il est utilisé 12lors de l'expansion automatiquement (comment l'expansion de notre analyse à l'arrière). L'expansion impliquera la migration des anciennes données, plus de consommation de performances, on peut estimer que dans HashMapl'élément total doit être stocké, nous vous recommandons de spécifier à l'avance HashMapla capacité de taille, pour éviter l'opération d'extension.

PS: Notez qu'en général, nous disons que la HashMapcapacité fait référence au nombre de seaux et que chaque seau peut mettre le nombre d'éléments dépend de la mémoire, donc la capacité ne veut pas dire que 16vous ne pouvez stocker 16qu'une keyvaleur.

Quelle est la capacité maximale de HashMap

Lorsque nous spécifions manuellement la capacité du HashMaptemps d' initialisation , appelez toujours la méthode suivante pour initialiser:

Insérez la description de l'image ici

Voir 453la ligne, lorsque la capacité est supérieure à notre MAXIMUM_CAPACITYtemps désigné , elle sera attribuée MAXIMUM_CAPACITY, et ce MAXIMUM_CAPACITYnombre est-il?

Insérez la description de l'image ici

Nous voyons la figure ci-dessus, MAXIMUM_CAPACITYest la 30 e puissance, et intla plage est la 31 e puissance moins 12, ce qui signifierait que la portée de le réduire? Voir la note ci-dessus permet de savoir où assurer HashMapla capacité 2de la puissance N-ème, et intla plage positive maximale de 31 types de puissance est inférieure à 2, il en a fallu 30 pour une puissance de deux.

On revient au constructeur précédent avec des paramètres, et enfin appelle une tableSizeForméthode, le rôle de cette méthode est d'ajuster HashMapla taille de capacité:

Insérez la description de l'image ici

Si vous ne comprenez pas les opérations au niveau du bit, vous ne comprendrez peut-être pas exactement ce que cela fait. En fait, une chose est faite dans cette méthode, qui consiste à ajuster la taille de la capacité spécifiée que nous avons transmise à la puissance de 2, donc au tout début, nous devons réduire le volume que nous avons passé 1, juste pour un ajustement unifié.

Prenons un exemple simple pour expliquer la méthode ci-dessus. L'opération sur bit implique du binaire, donc si la capacité que nous transmettons est un 5, alors elle est convertie en binaire 0000 0000 0000 0000 0000 0000 0000 0101. À ce stade , nous devons nous assurer que ce nombre est de 2 à la puissance N, alors le moyen le plus simple est de mettre notre nombre binaire actuel le plus à gauche 1, a été à l'extrême droite, tous les bits sont 1, alors le résultat est d'obtenir la N-ième puissance correspondante moins 12, comme nous passons à 5venir pour nous assurer que la N-ième puissance de 2, alors il faut sûrement l'ajuster pour la puissance 3 de deux, c'est-à-dire: 8cette fois-ci, moi avec vous devez faire est de remettre le réglage de la 3position 101est 111, vous pouvez obtenir 3 fois une puissance de deux moins un, la capacité totale finale plus 1 peut être ajustée à la Nième puissance de 2.

Ou 5, par exemple, dans une bonne 1position non signée à donner 0000 0000 0000 0000 0000 0000 0000 0010, puis la valeur d'origine 0000 0000 0000 0000 0000 0000 0000 0101pour effectuer l' |opération (uniquement des deux côtés en même temps 0, le résultat sera à 0), vous pouvez obtenir des résultats 0000 0000 0000 0000 0000 0000 0000 0111, c'est-à-dire devenir le second 1, cette fois indépendamment de combien plus loin au bon endroit, le bon nombre de fois, les résultats ne changeront pas, pour s'assurer après trois 1, et plus tard aussi tourner vers la droite, est de s'assurer que lorsque le premier nombre de 31bits 1, vous pouvez assurer le plus haut niveau d'addition en dehors du 31bit tout 1.

Ici, il faut douter de la volonté, pourquoi tant de peine à assurer HashMapla capacité, c'est-à-dire le nombre de barils de N 2 à la puissance de celui-ci?

Pourquoi la capacité de HashMap devrait être de 2 à la puissance N

La raison pour s'assurer que HashMapla capacité de N 2 à la puissance de la raison est très simple, est d'essayer d'éviter le résultat de hachage en une distribution inégale des données dans chaque seau distribution inégale, ce qui semble excessif certains éléments du seau, Affecter la requête Efficacité.

Nous continuons à regarder la putméthode, la ligne rouge dans la partie inférieure de la position d'indexation de l'algorithme FIG est calculée, c'est-à-dire par le tableau actuel (la HashMapcouche inférieure est l'utilisation d'un Nodetableau pour stocker des éléments) 长度 - 1re, et hashune valeur &obtenue par le calcul:

Insérez la description de l'image ici

&Il comporte l'opération est seulement deux nombres sont 1les résultats obtenus est 1, si ensuite n-1converti en binaire contient beaucoup de 0, comme 1000, cette fois encore et des hashvaleurs pour effectuer des &opérations, la plupart seulement 1dans cette position pour être efficace, toute la position de repos 0, qui équivaut à invalide. À ce stade, la probabilité de collision de hachage sera grandement améliorée. Et s'ils sont remplacés par un calcul 1111plus long et de hashvaleur &, les quatre étaient effectivement impliqués dans l'opération, réduisant considérablement la probabilité d'une collision de hachage, c'est pourquoi le meilleur moment pour commencer l'initialisation passe par une série d' |opérations pour être le premier un 1plus tard position de tous les éléments de toutes les modifications de la 1raison.

Parlez de l'opération de hachage dans HashMap

Ce qui précède, nous avons parlé d'une keyvaleur calculée lorsqu'elle est utilisée dans un emplacement qui a finalement atterri une hashvaleur, alors cette hashvaleur est de savoir comment l'obtenir?

Le chiffre est HashMapdes hashvaleurs calculées Méthode:

Insérez la description de l'image ici

Nous pouvons voir que cette méthode de calcul est très particulière, ce n'est pas simplement par une hashCodeméthode pour obtenir, en même temps mais aussi hashCodele décalage à droite non signé résultant 16puis après le bit XOR, pour donner le résultat final.

L'objectif est que le 16bit haut soit également impliqué dans le calcul, pour éviter davantage les collisions de hachage. Parce que HashMaptoujours à la puissance de la capacité N 2, donc si avec seulement un 16bit faible impliqué dans le calcul, donc une grande partie du cas à sa 16position basse est 1si haut- 16bit, le calcul peut également participer dans une certaine mesure, pour éviter les collisions de hachage. La raison derrière l'utilisation de l'opération exclusive ou sans l'utilisation de &et |la raison est que si l' &opération biaisait les résultats 1, l'utilisation de l' |opération biaiserait les résultats 0, ^conserver les caractéristiques d'origine de l'opération rendra les résultats meilleurs.

mettre le processus d'élément

putProcessus déjà mentionné ci-dessus, les méthodes précédentes, si elles HashMapne sont pas initialisées, vont s'initialiser, puis déterminer si la keyvaleur de position actuelle s'il y a un élément, si aucun élément, directement dans le dépôt, s'il y a des éléments de cette branche ira ci-dessous:

Insérez la description de l'image ici

Les elsebranches sont principalement 4logiques:

  1. Déterminez le courant keyet l'original keysont les mêmes, si le même retour direct.
  2. Si le courant keyet le précédent keyne sont pas égaux, il est jugé si l'élément actuel est stocké dans le TreeNodenœud de bac , s'il affiche actuellement des arbres noirs, un arbre rouge-noir conformément à l'algorithme stocké.
  3. Si l'actuel keyet le précédent keyne sont pas égaux et que le compartiment actuel est stocké dans une liste liée, chaque nœud, traversant le nextnœud est vide, vide directement dans l'élément de liste actuel, il n'est pas vide deux d'abord déterminer keysi le même, est égal au retour direct, différent de continuer le nextnœud de détermination , jusqu'à ce que le nœud keyégal ou nextvide soit vide.
  4. Après avoir inséré la liste liée, si la longueur de la liste liée actuelle est supérieure à la TREEIFY_THRESHOLDvaleur par défaut 8, la liste liée passera au stockage d'arbre rouge-noir.

Après le traitement, il y a un jugement final pour déterminer s'il faut ou non écraser les anciens éléments, si e != null, alors la keyvaleur actuelle existe déjà, déterminer la onlyIfAbsentvariable qui est la valeur par défaut false, représente l'ancienne valeur est écrasée, donc l'opération suivante sera couverte , puis Renvoyez l'ancienne valeur.

Insérez la description de l'image ici

Expansion

Lorsqu'après HashMapune grande quantité de données stockées dans la valeur de seuil (le nombre actuel de facteur de charge de godets *), il déclenchera l'opération d'expansion:

Insérez la description de l'image ici

Jetons donc un coup d'œil aux resizeméthodes:

Insérez la description de l'image ici

La première ligne rouge est déterminée si le courant a atteint une capacité MAXIMUM_CAPACITY, cette valeur est mentionnée devant une puissance de 2 soit 30, cette valeur est atteinte, l'expansion modifiera la valeur de seuil valeur intmaximale de type de stockage, qui n'est plus définie pour se développer.

La deuxième ligne rouge est l'expansion, l'expansion de la taille de la capacité est laissée de l'ancien 1lieu, qui est étendue aux 2temps d' origine . Bien entendu, si la capacité étendue ne remplit pas les conditions de la deuxième case rouge, elle sera définie dans la troisième case rouge.

Comment gérer les données d'origine après l'expansion

L'expansion est très simple, la clé est de savoir comment gérer les données d'origine? Sans regarder le code, nous pouvons trier grossièrement les scénarios de migration des données, et les scénarios sans données ne sont pas pris en compte:

  1. La position actuelle du godet est uniquement elle-même, c'est-à-dire qu'il n'y a pas d'autres éléments ci-dessous.
  2. Il y a des éléments sous la position actuelle du compartiment et il s'agit d'une structure de liste liée.
  3. Il y a des éléments sous la position actuelle du godet, et ce sont des arbres rouge-noir.

Regardons la source de la resizepartie migration des données:

Insérez la description de l'image ici

La partie de la boîte rouge est plus facile à comprendre. La première chose à faire est de voir si l'élément du compartiment actuel est solitaire. Si tel est le cas, recalculez simplement l'indice et attribuez-le aux nouvelles données. S'il s'agit d'un arbre rouge-noir, la réorganisation sera interrompue. Cette partie sera omise pour le moment. Avant, la dernière elsepartie est la partie de la chaîne de traitement, puis regardons le centre de la chaîne de traitement.

Traitement des données de la liste liée

La chaîne de traitement a une idée de base: la liste sous élément standard reste soit la même, soit dans l'original, basée sur une oldCaptaille plus .

Le code source de la partie complète du traitement des données de la liste chaînée est indiqué dans la figure suivante:

Insérez la description de l'image ici

La condition clé est que e.hash & oldCappourquoi ce résultat est égal à 0ce qu'il représente la position des éléments ne l'a pas changé?

Avant d'expliquer le problème, il est nécessaire de rappeler la tableSizeForméthode, qui sera n-1ajustée à une 00001111structure de données similaire , par exemple: capacité initiale d' 16une longueur telle n-1que 01111, et nque 10000, si e.hash & oldCap ==0cela signifie que hashla première valeur des 5bits est 0, l' 10000expansion après est obtenu 100000, correspondant à n-1cela 011111, et l'original ancien n-1aux différences est le premier 5bit (le premier 6bit 0n'affecte pas les résultats), donc quand e.hash & oldCap==0il montre la première 5place, il n'y a aucun effet sur les résultats, alors cette position ne changera pas, et si e.hash & oldCap !=0, il montre le premier 5chiffre affecte le résultat, et le premier 5bit si le résultat est calculé 1, la position cible est obtenue juste plus d'une oldCaptaille, c'est-à-dire 16. L'expansion des autres positions est la même, donc tant que la e.hash & oldCap==0position de l' indice reste inchangée, et si elle n'est pas égale 0, la position de l'indice doit en ajouter un de plus oldCap.

Une fois que la boucle finale a terminé le nœud, le code source de traitement est le suivant:

Insérez la description de l'image ici

L'empathie 32est 100000, ce qui explique une chose qui ne doit se concentrer que sur le bit le plus significatif 1peut être, car seules la médiane et la e.hashparticipation &pendant l'opération sont susceptibles d'obtenir 1,

Pour résumer

Cet article analyse HashMapcomment est initialisé, et putla méthode est comment mettre en place, également introduit afin d'éviter les collisions de hachage, la HashMapcapacité est fixée à N puissance de deux, finalement introduite HashMapdans l'expansion.

Je suppose que tu aimes

Origine blog.csdn.net/zwx900102/article/details/113849103
conseillé
Classement