HashMap源码分析

此源码分析JDK版本为1.7,只是简单分析,算是个人看源码的一点小总结,随意性比较强,专业的还需百度。
先简单介绍一下HashMap,HashMap数据结构为数组加链表,时间复杂度为O(1)(最理想状态下)。HashMap源码中,需要推敲的代码非常的多,个人觉得比ArrayList、LinkedList等要有营养很多。个人觉得HashMap最主要关注的几个点:1.put()方法,2.hash()方法,3.size设置。关于hash()和size设置推荐一个博客可了解一下:深入理解HashMap(及hash函数的真正巧妙之处)
#简介

1
2
3
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable{}

#属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//默认初始化长度16(可推敲一下为何为16)
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认扩容系数
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//空表实例
static final Entry<?,?>[] EMPTY_TABLE = {};
//数据数组(存放Entry的数组)
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
//map大小
transient int size;
//map阈值(到达该阈值扩容)
int threshold;
//扩容系数
final float loadFactor;
//修改次数
transient int modCount;
//默认的hash阈值
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
transient int hashSeed = 0;

#构造方法

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
//根据初始化长度和扩容系数创建HashMap
public HashMap(int initialCapacity, float loadFactor) {
//如果初始化长度小于0抛异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//如果初始化长度超过默认最大长度则设置初始化为默认最大长度
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//如果扩容系数小于等于0或者扩容系数不是一个合法的数字抛异常(比如参数为:0.0/0.0)
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);

//设置扩容系数
this.loadFactor = loadFactor;
//设置初始容量
threshold = initialCapacity;
//初始化(方法为空)
init();
}
//创建初始化的的HashMap
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//创建默认HashMap
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//创建一个新的Map,将m的键值对放入
public HashMap(Map<? extends K, ? extends V> m) {
//初始化长度和默认扩容因子的HashMap(根据默认的扩容因子计算是否小于默认大小,小于则按照默认大小)
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
//
inflateTable(threshold);

putAllForCreate(m);
}

#方法

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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
//返回格式化后大小
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";
//如果传入的值大于最大值,则返回最大值
//如果传入的的值大于1,则返回返回其对应的2的n次方值(Integer.highestOneBit(i))
//否则则返回1
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
//
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
//获取map大小
int capacity = roundUpToPowerOf2(toSize);
//获取map阈值
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//新建数组
table = new Entry[capacity];
//初始化HashSeed
initHashSeedAsNeeded(capacity);
}
//初始化HashSeed(每次扩容时会使用)
final boolean initHashSeedAsNeeded(int capacity) {
boolean currentAltHashing = hashSeed != 0;
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean switching = currentAltHashing ^ useAltHashing;
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
//hash实现
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}

h ^= k.hashCode();

// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
//根据哈希值和长度获取下标
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
//获取大小(真实数据的大小,非数组长度)
public int size() {
return size;
}
//判断大小是否为空
public boolean isEmpty() {
return size == 0;
}
//根据key获取值
public V get(Object key) {
//如果key为null,则根据null去获取值
if (key == null)
return getForNullKey();
//key不为null则根据key获取Entry
Entry<K,V> entry = getEntry(key);
//根据entry是否为null获取值
return null == entry ? null : entry.getValue();
}
//根据key为null获取值
private V getForNullKey() {
//如果size为0则直接返回null
if (size == 0) {
return null;
}
//因为key为null直接放在数组第一个位置,因此遍历数组第一个下得Entry链表即可
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
//如果没有则返回null
return null;
}
//根据key获取Entry
final Entry<K,V> getEntry(Object key) {
//如果size为0则直接返回null
if (size == 0) {
return null;
}
//根据key是否等于null获取key的hash值
int hash = (key == null) ? 0 : hash(key);
//根据hash值来获取该key对应的数组下标,遍历该下标下对应的链表
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
//如果hash值相等并且(key地址相同或者key相等)则返回
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
//说明并没有该值返回null
return null;
}
//是否包含键key
public boolean containsKey(Object key) {
//根据key获取Entry是否为null
return getEntry(key) != null;
}
//放入键值对
public V put(K key, V value) {
//如果数组为空则初始化数组
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//如果key为null则根据key为null放入
if (key == null)
return putForNullKey(value);
//获取key所对应的hash值
int hash = hash(key);
//获取该key所对应的hash值所对应数组的下标
int i = indexFor(hash, table.length);
//遍历该数组下标的链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果该key已经存在,则覆盖旧值并返回
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
//该方法为空,未知此方法干什么用
e.recordAccess(this);
return oldValue;
}
}
//修改次数增加
modCount++;
//到此步则说明没有改key,则新增Entry
addEntry(hash, key, value, i);
//新增Entry返回null
return null;
}
//放入Map
public void putAll(Map<? extends K, ? extends V> m) {
//获取m的大小
int numKeysToBeAdded = m.size();
//如果size为0则直接返回
if (numKeysToBeAdded == 0)
return;
//如果当前数组为空则扩容
if (table == EMPTY_TABLE) {
inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold));
}
//如果放入的m的size超过当前map的阈值则初始化
if (numKeysToBeAdded > threshold) {
//计算m扩容后的大小
int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
if (targetCapacity > MAXIMUM_CAPACITY)
targetCapacity = MAXIMUM_CAPACITY;
//计算当前数组扩容后的大小
int newCapacity = table.length;
//循环扩大(a<<=1 等价于 a = a << 1)
while (newCapacity < targetCapacity)
newCapacity <<= 1;
if (newCapacity > table.length)
resize(newCapacity);
}
//遍历m将entry放入当前map
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
//新增Entry
void addEntry(int hash, K key, V value, int bucketIndex) {
//如果size大于等与扩容阈值并且该下标不为空
if ((size >= threshold) && (null != table[bucketIndex])) {
//扩容
resize(2 * table.length);
//重新hash
hash = (null != key) ? hash(key) : 0;
//获取该hash对应的下标
bucketIndex = indexFor(hash, table.length);
}
创建Entry
createEntry(hash, key, value, bucketIndex);
}
//创建Entry
void createEntry(int hash, K key, V value, int bucketIndex) {
//获取该下标下的第一个Entry
Entry<K,V> e = table[bucketIndex];
//将新建的Entry放至该数组下链表第一个(新Entry的next指向就得Entry)
table[bucketIndex] = new Entry<>(hash, key, value, e);
//size+1
size++;
}
//扩容
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
//如果旧数组的长度已经到达最大,则返回将扩容阈值设置为最大值
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//新建该长度的数组
Entry[] newTable = new Entry[newCapacity];
//转换
transfer(newTable, initHashSeedAsNeeded(newCapacity));
//将table地址指向新的地址
table = newTable;
//设置扩容阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
//转换
void transfer(Entry[] newTable, boolean rehash) {
//定义数组长度
int newCapacity = newTable.length;
//遍历数组Entry
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
//如果重新hash则需要重新设置e的hash值
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//根据e的hash值获取e的新的下标
int i = indexFor(e.hash, newCapacity);
//将下标为i的Entry赋予为当前Entry的next
e.next = newTable[i];
//将当前Entry赋予newTable的i下标(因为上一步已经将之前的Entry赋予为当前的next,所以直接将引用指向新的即可)
newTable[i] = e;
//将下一个Entry赋予为e继续循环
e = next;
}
}
}
//根据key移除元素
public V remove(Object key) {
//根据key移除元素
Entry<K,V> e = removeEntryForKey(key);
//返回值
return (e == null ? null : e.value);
}
//根据key移除Entry
final Entry<K,V> removeEntryForKey(Object key) {
//如果当前大小为0则返回null
if (size == 0) {
return null;
}
//获取key的hash值
int hash = (key == null) ? 0 : hash(key);
//根据key的hash值返回下标
int i = indexFor(hash, table.length);
//获取当前下标的Entry
Entry<K,V> prev = table[i];
//将当前下标的Entry赋予给e
Entry<K,V> e = prev;
//循环判断
while (e != null) {
//获取当前Entry的下一个Entry
Entry<K,V> next = e.next;
//定义临时变量k
Object k;
//如果当前Entry的hash值和key的hash值相同并且(当前Entry的key和key地址相同或者值相同)则进入
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
//修改次数+1
modCount++;
//大小-1
size--;
//如果prev为当前Entry则说明该Entry是链表第一个元素,则直接将第一个元素指向该Entry的next即可
if (prev == e)
table[i] = next;
else
//如果不是链表的第一个元素则将当前元素上一个元素的next指向当前元素的下一个元素即可
prev.next = next;
//未知该代码什么作用(代码内容为空)
e.recordRemoval(this);
//返回旧值
return e;
}
//如果当前元素并不是要删除的元素则将当前元素赋予上一个元素prev,将下一个元素指向当前元素e,继续循环
prev = e;
e = next;
}
//走到这一步说明没有该元素,此时e已经为null,返回
return e;
}
//删除Entry
final Entry<K,V> removeMapping(Object o) {
//如果当前map大小为0或者不是MapEntry类型则返回null
if (size == 0 || !(o instanceof Map.Entry))
return null;
//强转为Map.Entry类型
Map.Entry<K,V> entry = (Map.Entry<K,V>) o;
//获取key
Object key = entry.getKey();
//下方代码和removeEntryForKey一致除了该判断是判断entry的hash和值(个人觉得完全可以在此调用根据key删除)
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);

Entry<K,V> prev = table[i];
Entry<K,V> e = prev;

while (e != null) {
Entry<K,V> next = e.next;
if (e.hash == hash && e.equals(entry)) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}

return e;
}
//清空map
public void clear() {
//修改次数+1
modCount++;
//调用工具类(内部实现为遍历数组,将数组每个下标的元素指向null,此时扩容阈值threshold还是旧的)
Arrays.fill(table, null);
//设置大小为0
size = 0;
}
//是否包含值
public boolean containsValue(Object value) {
//如果值为null则调用是否包含空值方法
if (value == null)
return containsNullValue();
//定义临时变量指向当前数组
Entry[] tab = table;
//遍历数组
for (int i = 0; i < tab.length ; i++)
//遍历当前Entry链表
for (Entry e = tab[i] ; e != null ; e = e.next)
//如果相同则返回true
if (value.equals(e.value))
return true;
//说明不好看此值
return false;
}
//是否包含空值(私有)
private boolean containsNullValue() {
//方法体和判断值相同,只不过是判断是否为null
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (e.value == null)
return true;
return false;
}
//克隆
public Object clone() {
HashMap<K,V> result = null;
try {
调用Object的克隆方法(浅克隆)
result = (HashMap<K,V>)super.clone();
} catch (CloneNotSupportedException e) {
// assert false;
}
if (result.table != EMPTY_TABLE) {
//刷新HashSeed
result.inflateTable(Math.min(
(int) Math.min(
size * Math.min(1 / loadFactor, 4.0f),
// we have limits...
HashMap.MAXIMUM_CAPACITY),
table.length));
}
//以下类似于初始化
result.entrySet = null;
result.modCount = 0;
result.size = 0;
result.init();
//此处将旧的数据放入克隆之后的result
result.putAllForCreate(this);
//返回result
return result;
}
//tag:Entry比较简单,代码量也少,就不分析了

#总结

1
2
3
4
1.获取值是先计算其hash值,再根据其hash值获取数组下标,然后遍历该下标对应的链表去取值
2.放入键值对则是先计算key的hash值,根据hash值获取数组下标,遍历查看是否有其对应的值,有则覆盖,没有则新增Entry
3.新增Entry时会先判断是否需要扩容,再创建Entry。创建Entry只需要将新的Entry放置该数组位置,将next指向旧的头节点
4.扩容是两倍扩容,根据是否重新hash而决定是否更改Entry的hash值,然后遍历获取新的数组下标将旧数据放置新的数组中,再将旧的数组指向新的数组

HashMap源码分析
https://gallrax.github.io/2018/04/04/HashMap源码分析/
作者
Gallrax
发布于
2018年4月4日
许可协议