Lisez facilement le code source de HashMap
- Préface
- Analyse du code source HashMap
-
- Stockage de données dans HashMap
- Pourquoi est-il recommandé de spécifier la taille lors de l'initialisation du HashMap
- Quelle est la capacité maximale de HashMap
- Pourquoi la capacité de HashMap devrait être de 2 à la puissance N
- Parlez de l'opération de hachage dans HashMap
- mettre le processus d'élément
- Expansion
- Pour résumer
Préface
HashMap
Pour chaque étude, les Java
gens 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 HashMap
le code source.
Analyse du code source HashMap
HashMap
Sur la base d'une table de hachage, et implémente Map
une interface dans laquelle la key-value
structure de données de paire clé-valeur y est stockée key
et dont value
les null
valeurs 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 put
opération sera sur le key
hachage, 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 key
valeur actuelle de l'emplacement de stockage, puisqu'il est haché, alors il y aura certainement une collision de hachage , qui dans jdk1.7
les versions précédentes et chaque index est stocké dans une liste chaînée, et la jdk1.8
version et au-delà, alors cela est optimisé lorsque la longueur de la chaîne est plus longue que 8
le temps, sera un arbre rouge-noir en mémoire, lorsque les éléments d'arbre rouge-noir à partir d'une 8
diminution 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.8
une HashMap
vue schématique de la structure de stockage (chacune appelée position cible 桶
):
Dans HashMap
le 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
HashMap
Initialisation, nous recommandons généralement une estimation de la taille possible, puis de construire HashMap
la capacité spécifiée lorsque l'objet, c'est pourquoi? Pour répondre à cette question, voyons HashMap
comment initialiser.
Lorsque nous ne spécifions aucun paramètre pour créer la figure est HashMap
soumise:
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 HashMap
utilisé 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, HashMap
et n'initialisons pas une capacité, alors nous pouvons nous aventurer à deviner quand nous appelons put
la méthode actuelle sera certainement jugée HashMap
si initialisée, sinon initialisée, elle le sera Initialisez d'abord.
Capacité par défaut de HashMap
put
La méthode détermine le courant HashMap
si est initialisé, il n'est pas initialisé, il appellera la resize
méthode est initialisé, resize
la 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:
On peut voir, le HashMap
temps d' initialisation , principalement des 2
variables initialisées : une newCap
représentant le courant HashMap
en nombre de barils, un indice du tableau représente un seau; on est newThr
principalement 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 HashMap
temps sans spécifier la taille du seau lorsqu'il est utilisé 12
lors 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 HashMap
l'élément total doit être stocké, nous vous recommandons de spécifier à l'avance HashMap
la capacité de taille, pour éviter l'opération d'extension.
PS: Notez qu'en général, nous disons que la HashMap
capacité 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 16
vous ne pouvez stocker 16
qu'une key
valeur.
Quelle est la capacité maximale de HashMap
Lorsque nous spécifions manuellement la capacité du HashMap
temps d' initialisation , appelez toujours la méthode suivante pour initialiser:
Voir 453
la ligne, lorsque la capacité est supérieure à notre MAXIMUM_CAPACITY
temps désigné , elle sera attribuée MAXIMUM_CAPACITY
, et ce MAXIMUM_CAPACITY
nombre est-il?
Nous voyons la figure ci-dessus, MAXIMUM_CAPACITY
est la 30 e puissance, et int
la 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 HashMap
la capacité 2
de la puissance N-ème, et int
la 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 tableSizeFor
méthode, le rôle de cette méthode est d'ajuster HashMap
la taille de capacité:
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 à 5
venir 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: 8
cette fois-ci, moi avec vous devez faire est de remettre le réglage de la 3
position 101
est 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 1
position non signée à donner 0000 0000 0000 0000 0000 0000 0000 0010
, puis la valeur d'origine 0000 0000 0000 0000 0000 0000 0000 0101
pour 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 31
bits 1
, vous pouvez assurer le plus haut niveau d'addition en dehors du 31
bit tout 1
.
Ici, il faut douter de la volonté, pourquoi tant de peine à assurer HashMap
la 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 HashMap
la 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 put
mé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 HashMap
couche inférieure est l'utilisation d'un Node
tableau pour stocker des éléments) 长度 - 1
re, et hash
une valeur &
obtenue par le calcul:
&
Il comporte l'opération est seulement deux nombres sont 1
les résultats obtenus est 1
, si ensuite n-1
converti en binaire contient beaucoup de 0
, comme 1000
, cette fois encore et des hash
valeurs pour effectuer des &
opérations, la plupart seulement 1
dans 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 1111
plus long et de hash
valeur &
, 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 1
plus tard position de tous les éléments de toutes les modifications de la 1
raison.
Parlez de l'opération de hachage dans HashMap
Ce qui précède, nous avons parlé d'une key
valeur calculée lorsqu'elle est utilisée dans un emplacement qui a finalement atterri une hash
valeur, alors cette hash
valeur est de savoir comment l'obtenir?
Le chiffre est HashMap
des hash
valeurs calculées Méthode:
Nous pouvons voir que cette méthode de calcul est très particulière, ce n'est pas simplement par une hashCode
méthode pour obtenir, en même temps mais aussi hashCode
le décalage à droite non signé résultant 16
puis après le bit XOR, pour donner le résultat final.
L'objectif est que le 16
bit haut soit également impliqué dans le calcul, pour éviter davantage les collisions de hachage. Parce que HashMap
toujours à la puissance de la capacité N 2, donc si avec seulement un 16
bit faible impliqué dans le calcul, donc une grande partie du cas à sa 16
position basse est 1
si haut- 16
bit, 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
put
Processus déjà mentionné ci-dessus, les méthodes précédentes, si elles HashMap
ne sont pas initialisées, vont s'initialiser, puis déterminer si la key
valeur 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:
Les else
branches sont principalement 4
logiques:
- Déterminez le courant
key
et l'originalkey
sont les mêmes, si le même retour direct. - Si le courant
key
et le précédentkey
ne sont pas égaux, il est jugé si l'élément actuel est stocké dans leTreeNode
nœud de bac , s'il affiche actuellement des arbres noirs, un arbre rouge-noir conformément à l'algorithme stocké. - Si l'actuel
key
et le précédentkey
ne sont pas égaux et que le compartiment actuel est stocké dans une liste liée, chaque nœud, traversant lenext
nœud est vide, vide directement dans l'élément de liste actuel, il n'est pas vide deux d'abord déterminerkey
si le même, est égal au retour direct, différent de continuer lenext
nœud de détermination , jusqu'à ce que le nœudkey
égal ounext
vide soit vide. - Après avoir inséré la liste liée, si la longueur de la liste liée actuelle est supérieure à la
TREEIFY_THRESHOLD
valeur par défaut8
, 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 key
valeur actuelle existe déjà, déterminer la onlyIfAbsent
variable 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.
Expansion
Lorsqu'après HashMap
une 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:
Jetons donc un coup d'œil aux resize
méthodes:
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 int
maximale 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 1
lieu, qui est étendue aux 2
temps 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:
- 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.
- Il y a des éléments sous la position actuelle du compartiment et il s'agit d'une structure de liste liée.
- Il y a des éléments sous la position actuelle du godet, et ce sont des arbres rouge-noir.
Regardons la source de la resize
partie migration des données:
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 else
partie 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 oldCap
taille 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:
La condition clé est que e.hash & oldCap
pourquoi ce résultat est égal à 0
ce 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 tableSizeFor
méthode, qui sera n-1
ajustée à une 00001111
structure de données similaire , par exemple: capacité initiale d' 16
une longueur telle n-1
que 01111
, et n
que 10000
, si e.hash & oldCap ==0
cela signifie que hash
la première valeur des 5
bits est 0
, l' 10000
expansion après est obtenu 100000
, correspondant à n-1
cela 011111
, et l'original ancien n-1
aux différences est le premier 5
bit (le premier 6
bit 0
n'affecte pas les résultats), donc quand e.hash & oldCap==0
il montre la première 5
place, 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 5
chiffre affecte le résultat, et le premier 5
bit si le résultat est calculé 1
, la position cible est obtenue juste plus d'une oldCap
taille, c'est-à-dire 16
. L'expansion des autres positions est la même, donc tant que la e.hash & oldCap==0
position 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:
L'empathie 32
est 100000
, ce qui explique une chose qui ne doit se concentrer que sur le bit le plus significatif 1
peut être, car seules la médiane et la e.hash
participation &
pendant l'opération sont susceptibles d'obtenir 1
,
Pour résumer
Cet article analyse HashMap
comment est initialisé, et put
la méthode est comment mettre en place, également introduit afin d'éviter les collisions de hachage, la HashMap
capacité est fixée à N puissance de deux, finalement introduite HashMap
dans l'expansion.