Hashmap源码解析-扩容函数

系列目录#

  1. 总览&目录
  2. 链表节点NODE
  3. 构造函数
  4. 扩容函数
  5. put
  6. remove
  7. get
  8. 遍历
  9. &hashtable

扩容函数#

1.更新容量和阈值,

​ if cap>0, 不超过上限的情况下cap、thre都乘2

​ if cap=0, oldThr>0, 说明初始化的时候赋初始容量参数了,newCap=oldThr

​ if cap=0, oldthr=0, 直接重新初始化,cap=16,thre=12

2.更新哈希桶, 遍历原桶

​ if 只有一个节点,直接挪过去

​ if 链表有超过8个节点,是红黑树, 复杂, 再说todo

​ if 少于8个的链表,则可能挪到低位,也可能挪到高位,看它本身hash在新容量时应在哪里,代码中巧妙通过与oldCap & 的方式判断需改到高还是低,具体在代码注释中有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
final Node<K,V>[] resize() {

//oldTab 为当前表的哈希桶

Node<K,V>[] oldTab = table;

//当前哈希桶的容量 length

int oldCap = (oldTab == null) ? 0 : oldTab.length;

//当前的阈值

int oldThr = threshold;

//初始化新的容量和阈值为0

int newCap, newThr = 0;

//如果当前容量大于0

if (oldCap > 0) {

//如果当前容量已经到达上限

if (oldCap >= MAXIMUM_CAPACITY) {

//则设置阈值是2的31次方-1

threshold = Integer.MAX_VALUE;

//同时返回当前的哈希桶,不再扩容

return oldTab;

}//否则新的容量为旧的容量的两倍。

else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&

oldCap >= DEFAULT_INITIAL_CAPACITY) //如果旧的容量大于等于默认初始容量16

//那么新的阈值也等于旧的阈值的两倍

newThr = oldThr << 1; // double threshold

}//如果当前表是空的,但是有阈值。代表是初始化时指定了容量、阈值的情况

else if (oldThr > 0) // initial capacity was placed in threshold

newCap = oldThr;//那么新表的容量就等于旧的阈值

else { //如果当前表是空的,而且也没有阈值。代表是初始化时没有任何容量/阈值参数的情况 // zero initial threshold signifies using defaults

newCap = DEFAULT_INITIAL_CAPACITY;//此时新表的容量为默认的容量 16

newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//新的阈值为默认容量16 * 默认加载因子0.75f = 12

}

if (newThr == 0) {//如果新的阈值是0,对应的是 当前表是空的,但是有阈值的情况

float ft = (float)newCap * loadFactor;//根据新表容量 和 加载因子 求出新的阈值

//进行越界修复

newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?

(int)ft : Integer.MAX_VALUE);

}

//更新阈值

threshold = newThr;

@SuppressWarnings({"rawtypes","unchecked"})

//根据新的容量 构建新的哈希桶

Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];

//更新哈希桶引用

table = newTab;

//如果以前的哈希桶中有元素

//下面开始将当前哈希桶中的所有节点转移到新的哈希桶中

if (oldTab != null) {

//遍历老的哈希桶

for (int j = 0; j < oldCap; ++j) {

//取出当前的节点 e

Node<K,V> e;

//如果当前桶中有元素,则将链表赋值给e

if ((e = oldTab[j]) != null) {

//将原哈希桶置空以便GC

oldTab[j] = null;

//如果当前链表中就一个元素,(没有发生哈希碰撞)

if (e.next == null)

//直接将这个元素放置在新的哈希桶里。

//注意这里取下标 是用 哈希值 与 桶的长度-1 。 由于桶的长度是2的n次方,这么做其实是等于 一个模运算。但是效率更高

newTab[e.hash & (newCap - 1)] = e;

//如果发生过哈希碰撞 ,而且是节点数超过8个,转化成了红黑树(暂且不谈 避免过于复杂, 后续专门研究一下红黑树)

else if (e instanceof TreeNode)

((TreeNode<K,V>)e).split(this, newTab, j, oldCap);

//如果发生过哈希碰撞,节点数小于8个。则要根据链表上每个节点的哈希值,依次放入新哈希桶对应下标位置。

else { // preserve order

//因为扩容是容量翻倍,所以原链表上的每个节点,现在可能存放在原来的下标,即low位, 或者扩容后的下标,即high位。 high位= low位+原哈希桶容量

//低位链表的头结点、尾节点

Node<K,V> loHead = null, loTail = null;

//高位链表的头节点、尾节点

Node<K,V> hiHead = null, hiTail = null;

Node<K,V> next;//临时节点 存放e的下一个节点

do {

next = e.next;

// 此处操作跟hash计算索引有关

// 在 HashMap 中,索引的计算方法为 (n - 1) & hash

// 所以,在进行扩容操作 (n*2) 后,该计算结果可能导致变更

// 例如

// 有一个值为 111001 的 hash

// 扩容前 n=16(10000) n-1=15(1111) (n - 1) & hash = 1111 & 111001= 001001

// 扩容后 n=32(100000) n-1=31(11111) (n - 1) & hash = 11111 & 111001= 011001

// 假如 hash 值为 101001

// 那么会发现扩容前 1111 & 101001 = 001001

// 扩容后 11111 & 101001 = 001001

// 所以可知,在进行扩容操作时,主要按照 hash 与 原数组长度中1的对应位置有关

// 如果 hash 中对应的位置为0,扩容后索引结果不变

// 不为0,表示索引结果为原结果+原数组长度

// 而 hash 中该对应位置的值只存在俩种可能 0,1

// 所以在该节点中的数据大约有一半索引不变,一半为原索引+原数组长度

// 通过 e.hash & oldCap 的方式可以得知 hash 在 oldCap 1对应的位置是否为0或1

if ((e.hash & oldCap) == 0) {

//给头尾节点指针赋值

if (loTail == null)

loHead = e;

else

loTail.next = e;

loTail = e;

}//高位也是相同的逻辑

else {

if (hiTail == null)

hiHead = e;

else

hiTail.next = e;

hiTail = e;

}//循环直到链表结束

} while ((e = next) != null);

//将低位链表存放在原index处,

if (loTail != null) {

loTail.next = null;

newTab[j] = loHead;

}

//将高位链表存放在新index处

if (hiTail != null) {

hiTail.next = null;

newTab[j + oldCap] = hiHead;

}

}

}

}

}

return newTab;

}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×