文章目录
  1. 1. HashMap简介
  2. 2. 运行环境
  3. 3. 源码分析
  4. 4. 几点总结

HashMap简介

HashMap是基于哈希表和链表实现的,里面的每一个元素都是键值对的形式,通过单链表的形式解决键冲突问题,超过阀值自动扩容。HashMap的size最好是2的倍数,默认大小是16,默认加载因子是0.75

运行环境

  • OS:Win7 64bit
  • idea:IntelliJ IDEA 2017
  • jdkVersion:1.7.0_79 64 bit
  • 使用的pom.xml:无

源码分析

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
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
/**
* 默认的初始化容量。必须是2的倍数
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* HashMap的最大容量,如果指定的参数比该值大则使用该值替换。必须要小于等于1<<30(1073741824)
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认加载因子,0.75f
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 一个空的hash数组
*/
static final Entry<?,?>[] EMPTY_TABLE = {};
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
/**
* The number of key-value mappings contained in this map.
* 该map包含key-value对的个数
*/
transient int size;
/**
* 当HashMap的大小到达该值时则需要进行调整大小
*/
int threshold;
/**
* 指定Map的加载因子
* @serial
*/
final float loadFactor;
/**
* 记录HashMap被修改的次数
*/
transient int modCount;
/**
* capacity and load factor.
* 通过指定初始化容量和加载因子创建一个空的HashMap
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
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
* (16) and the default load factor (0.75).
*/
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
* 创建一个新的HashMap包含指定Map的键值对。创建的HashMap使用默认的加载因子,初始容量计算方式是:
* 比较指定map的size/DEFAULT_LOAD_FACTOR+1和DEFAULT_INITIAL_CAPACITY的大小,取大的那个
*/
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);
putAllForCreate(m);
}
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
/**
* HashMap集合的table属性进行大小设置
*/
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);
//获取下次扩容的大小
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//在第一次put的时候设置table的大小,默认16.
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
/**
* 返回k对应的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);
}
/**
* 返回h在entry数组的索引值
* 这里是有&替换取模,为了提高效率
* h & (length-1)也保证了返回的index不会大于length
*/
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);
}
/**
* 返回该map的键值对个数
*/
public int size() {
return size;
}
/**
* 如果该map不包含键值对则返回true
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 返回指定的key包含的value,如果指定的key不存则返回null.
* 如果指定的key是null,则比较key==null,如果不是null,则比较key.equal(k)
*/
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
/**
* 将查找key为null单独出来
*/
private V getForNullKey() {
if (size == 0) {
return null;
}
//为空的key的位置永远在第0个
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
/**
* 如果包含指定的可以则返回true
*/
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
/**
* 返回指定key相关的entry。如果不包含这个key则返回null
*/
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);//通过key计算hash值
for (Entry<K,V> e = table[indexFor(hash, table.length)];//通过hash值和length计算,该hash值在table数组的位置
e != null;
e = e.next) {//查找下一个,这里也体现出了使用了链表的形式存储
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) //先比较key对应的hash值,再比较key的值是否相等
return e;
}
return null;
}
/**
* 添加指定的键值对到该集合中。如果指定的key存在,则原来的value会被替换掉
*/
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
/**
* key为null的put方法
*/
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
/**
* 这个方法是被构造方法或伪构造方法(clone, readObject)调用,他不会重置这个table的size。
* 该方法调用createEntry方法而不是addEntry方法
*/
private void putForCreate(K key, V value) {
int hash = null == key ? 0 : hash(key);
int i = indexFor(hash, table.length);
/**
* Look for preexisting entry for key. This will never happen for
* clone or deserialize. It will only happen for construction if the
* input Map is a sorted map whose ordering is inconsistent w/ equals.
*/
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
e.value = value;
return;
}
}
createEntry(hash, key, value, i);
}
private void putAllForCreate(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
putForCreate(e.getKey(), e.getValue());
}
/**
* 重新计算hash值所处槽index,添加到一个新的entry数组中。
* 当键值对的数量到达扩容临界值时将会自动调用。
*/
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 = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
/**
* 将当前的hashMap的entry数组对象转换到一个新的entry数组中
* 该方法会遍历整个entry数组,而且还需要重新计算entry数组元素的每个hash,是比较耗时。
* 所以在HashMap初始化的时候就应该指定大小
*/
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
/**
* 复制指定map的所有键值对到当前map中,如果key相同将会被覆盖
*/
public void putAll(Map<? extends K, ? extends V> m) {
int numKeysToBeAdded = m.size();
if (numKeysToBeAdded == 0)
return;
if (table == EMPTY_TABLE) {
inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold));
}
/*
* 添加的时候有可能需要扩大容量
*/
if (numKeysToBeAdded > threshold) {
int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
if (targetCapacity > MAXIMUM_CAPACITY)
targetCapacity = MAXIMUM_CAPACITY;
int newCapacity = table.length;
while (newCapacity < targetCapacity)
newCapacity <<= 1;
if (newCapacity > table.length)
resize(newCapacity);
}
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
/**
* 如果存在key则从当前的map中删除指定的key的键值对,并返回键值对的值
* 否则返回null
*/
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
/**
* 在当前HashMap中移除和返回指定key。
* 返回null表示当前map不存在指定的key
*/
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
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;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
/**
* Special version of remove for EntrySet using {@code Map.Entry.equals()}
* for matching.
*/
final Entry<K,V> removeMapping(Object o) {
if (size == 0 || !(o instanceof Map.Entry))
return null;
Map.Entry<K,V> entry = (Map.Entry<K,V>) o;
Object key = entry.getKey();
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;
}
/**
* Removes all of the mappings from this map.
* The map will be empty after this call returns.
* 删除所有的键值对,map为空
*/
public void clear() {
modCount++;
//将指定数组的每一个元素置为null
Arrays.fill(table, null);
size = 0;
}
/**
* 如果有一个或多个key包含指定的value,则返回true
*/
public boolean containsValue(Object value) {
if (value == null)
return containsNullValue();
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)//循环遍历该数组
for (Entry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value))
return true;
return false;
}
/**
* 查找null值
*/
private boolean containsNullValue() {
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;
}
/**
* 返回一个浅拷贝的HashMap实例,键值对是不会拷贝的
*/
public Object clone() {
HashMap<K,V> result = null;
try {
result = (HashMap<K,V>)super.clone();
} catch (CloneNotSupportedException e) {
// assert false;
}
if (result.table != EMPTY_TABLE) {
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.putAllForCreate(this);
return result;
}
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
public final String toString() {
return getKey() + "=" + getValue();
}
/**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/
void recordAccess(HashMap<K,V> m) {
}
/**
* This method is invoked whenever the entry is
* removed from the table.
*/
void recordRemoval(HashMap<K,V> m) {
}
}
/**
* 添加一个新的entry对象通过指定的key、value和hash code到指定的槽
* 如果size大于等于扩容临界值且计算出槽的位置不等于null,则进行扩容
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
//扩容为原来的两倍
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
/**
* 通过hash,key,value,槽index创建一个Entry对象
*/
void createEntry(int hash, K key, V value, int bucketIndex) {
//获取指定槽index的entry对象
Entry<K,V> e = table[bucketIndex];
//指定槽index新建该对象,注意最后一个参数,如果原来的槽有值,则会将原来的值挂在新创建的entry对象的next属性上
//这样就形成了一个链表结构,解决了hash冲突的问题
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
private abstract class HashIterator<E> implements Iterator<E> {
Entry<K,V> next; // next entry to return
int expectedModCount; // For fast-fail
int index; // current slot
Entry<K,V> current; // current entry
HashIterator() {
expectedModCount = modCount;
if (size > 0) { // advance to first entry
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
}
public final boolean hasNext() {
return next != null;
}
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
}
private final class ValueIterator extends HashIterator<V> {
public V next() {
return nextEntry().value;
}
}
private final class KeyIterator extends HashIterator<K> {
public K next() {
return nextEntry().getKey();
}
}
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
public Map.Entry<K,V> next() {
return nextEntry();
}
}
// Subclass overrides these to alter behavior of views' iterator() method
Iterator<K> newKeyIterator() {
return new KeyIterator();
}
Iterator<V> newValueIterator() {
return new ValueIterator();
}
Iterator<Map.Entry<K,V>> newEntryIterator() {
return new EntryIterator();
}
// Views
private transient Set<Map.Entry<K,V>> entrySet = null;
/**
* 返回key对应的集合,其实就是KeySet对象
*/
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
//KeySet继承AbstractSet,说明该集合中没有重复的key
private final class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
/**
* 返回value的集合。
* 这个集合是map value的一个备份,如果map的值修改,则该集合也会被修改
*/
public Collection<V> values() {
Collection<V> vs = values;
return (vs != null ? vs : (values = new Values()));
}
private final class Values extends AbstractCollection<V> {
public Iterator<V> iterator() {
return newValueIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
HashMap.this.clear();
}
}
/**
* 返回map集合的Entry集合。
* 如果map修改了,返回的entry也会被修改
*/
public Set<Map.Entry<K,V>> entrySet() {
return entrySet0();
}
private Set<Map.Entry<K,V>> entrySet0() {
Set<Map.Entry<K,V>> es = entrySet;
return es != null ? es : (entrySet = new EntrySet());
}
//EntrySet继承AbstractSet,说明没有重复的
private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return newEntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<K,V> e = (Map.Entry<K,V>) o;
Entry<K,V> candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
public boolean remove(Object o) {
return removeMapping(o) != null;
}
public int size() {
return size;
}
public void clear() {
HashMap.this.clear();
}
}
// 返回容量
int capacity() { return table.length; }
// 返回加载因子
float loadFactor() { return loadFactor; }
}

几点总结

  • 首先HashMap的数据结构是数组和链表的形式,具体入下图
    通过源码查看链表的数据结构:
    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
    //Entry是一个单链表,实现Map.Entry接口,所以实现了K getKey();、V getValue();、V setValue(V value);、
    //boolean equals(Object o);和 int hashCode();方法
    static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;//下一个entry对象
    int hash;
    /**
    * 构造方法
    * 通过h(哈希),k(key值),v(value),n(下一个节点元素)
    */
    Entry(int h, K k, V v, Entry<K,V> n) {
    value = v;
    next = n;
    key = k;
    hash = h;
    }
    public final K getKey() {
    return key;
    }
    public final V getValue() {
    return value;
    }
    public final V setValue(V newValue) {
    V oldValue = value;
    value = newValue;
    return oldValue;
    }
    //重写equals方法
    //判断两个entry是否相等,当两个entry对象的key和value都相等时返回true
    public final boolean equals(Object o) {
    if (!(o instanceof Map.Entry))
    return false;
    Map.Entry e = (Map.Entry)o;
    Object k1 = getKey();
    Object k2 = e.getKey();
    if (k1 == k2 || (k1 != null && k1.equals(k2))) {
    Object v1 = getValue();
    Object v2 = e.getValue();
    if (v1 == v2 || (v1 != null && v1.equals(v2)))
    return true;
    }
    return false;
    }
    public final int hashCode() {
    return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
    }
    public final String toString() {
    return getKey() + "=" + getValue();
    }
    /**
    * 当添加元素时会调用该方法
    */
    void recordAccess(HashMap<K,V> m) {
    }
    /**
    * 当移除元素时会调用该方法
    */
    void recordRemoval(HashMap<K,V> m) {
    }
    }

该类的结构除了key、value、hash还有一个next节点。

  • HashMap有四个构造方法,有两个参数非常重要,initialCapacity 初始化容量和loadFactor 加载因子。其中容量表示哈希表中槽的数量,从源码中可以看出如果不给定默认是16.加载因子表示在其容量自动增加之前可以达到多满的一种尺度,当哈希表中的数据大于等于当前槽的数量乘以加载因子,则需要将HashMap进行resize()操作(即扩容)

    • 介绍一下加载因子,如果加载因子越大则对空间利用更加充分,但是查询效率降低(链表长度会越来越长);如果加载因子设置的过小,则哈希表中的数据过于稀疏(很多空间没有使用到,就开始扩容),对空间造成严重的浪费。如果我们构造参数不指定,默认是0.75,这是一个比较理想的值,我们无需修改。
    • 另外不管我们指定了多少容量,构造方法都将实际容量设置为不小于给定容量的2的次方的一个数。容量最大值是2的30次方
  • HashMap的key和value都允许为null

  • 上面涉及到HashMap的扩容,下面通过源码分析一下扩容代码
    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
    void addEntry(int hash, K key, V value, int bucketIndex) {
    //当哈希表中的数据量大于等于当前槽的数量乘以加载因子且计算出来的索引值在哈希表有值时,
    //进行扩容操作
    if ((size >= threshold) && (null != table[bucketIndex])) {
    resize(2 * table.length);//扩容的大小是原来的两倍
    hash = (null != key) ? hash(key) : 0;
    bucketIndex = indexFor(hash, table.length);
    }
    createEntry(hash, key, value, bucketIndex);
    }
    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 = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
    //循环计算原哈希数组的key的index并放到新的哈希数组中
    void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
    while(null != e) {
    Entry<K,V> next = e.next;
    if (rehash) {
    e.hash = null == e.key ? 0 : hash(e.key);
    }
    int i = indexFor(e.hash, newCapacity);
    e.next = newTable[i];
    newTable[i] = e;
    e = next;
    }
    }
    }

很明显,扩容的时候会将新的哈希数组扩大到原来的两倍,并创建一个新的哈希数组,然后循环将原hash表的数据放到新的哈希数组中,在循环过程中还需要重新计算每个key的hash值。这很明显的说明进行扩容是一个相当耗时的操作,因此,我们在使用HashMap的时候最好能提前预估一下元素个数,这样有助于提高HashMap的性能。

  • 下面来重点分析一下最常用的两个方法get和put,先从简单的get开始,如下:
    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
    //分为key为空和不为空两种情况处理,没有则返回null
    public V get(Object key) {
    if (key == null)
    return getForNullKey();
    Entry<K,V> entry = getEntry(key);
    return null == entry ? null : entry.getValue();
    }
    //key为null的调用方法
    private V getForNullKey() {
    if (size == 0) {
    return null;
    }
    //表示key为空元素存储在哈希数组的第0个位置,但是也有可能在0号位置对应的链表中
    //所以这里使用了循环
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
    if (e.key == null)
    return e.value;
    }
    return null;
    }
    final Entry<K,V> getEntry(Object key) {
    if (size == 0) {
    return null;
    }
    //如果key为空则返回0,否则返回对应的key的hash
    int hash = (key == null) ? 0 : hash(key);
    //计算key在哈希数组的位置,然后循环哈希数组的链表
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
    e != null;
    e = e.next) {
    Object k;
    if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k))))
    //比较key是否相等,相等则返回entry对象
    return e;
    }
    return null;
    }

如果key为null,则直接从哈希表的第一个位置[0]对应的链表上进行查找。注意这里:key为null永远存在以table[0]为头的链表中,当然这里不一定就存在table[0]中。如果不为null,则先通过key计算hash值,根据hash值和length找到table中的索引,在该索引对应的链表中查找是否有键和目标键相等,有就返回value,否则返回null。

  • put方法详解,源码如下:

    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
    public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
    inflateTable(threshold);
    }
    if (key == null)
    return putForNullKey(value);
    int hash = hash(key);//计算key的hash值
    int i = indexFor(hash, table.length);//通过hash和length计算索引位置
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {//循环遍历索引位置的链表
    Object k;
    if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//如果hash值相等且key相同则用新value替换旧的value
    V oldValue = e.value;
    e.value = value;
    e.recordAccess(this);
    return oldValue;//返回旧的value
    }
    }
    modCount++;
    addEntry(hash, key, value, i);
    return null;
    }
    //key为空执行方法
    private V putForNullKey(V value) {
    //null将存在hash表的0号位置,从0号索引链表进行遍历
    //找到key的将替换value并将原来的value返回
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
    if (e.key == null) {
    V oldValue = e.value;
    e.value = value;
    e.recordAccess(this);
    return oldValue;
    }
    }
    modCount++;
    addEntry(0, null, value, 0);
    return null;
    }
    void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
    //当哈希表中的数据量大于等于当前槽的数量乘以加载因子且计算出来的索引值在哈希表有值时,
    //进行扩容操作,扩容的可以看前面的介绍
    resize(2 * table.length);
    hash = (null != key) ? hash(key) : 0;
    bucketIndex = indexFor(hash, table.length);
    }
    createEntry(hash, key, value, bucketIndex);
    }
    //通过hash值、key、value、bucketIndex创建一个Entry对象
    void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    //新创建的Entry对象的next的值指向的是原来索引位置的值
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
    }

    如果key为空,则将将其添加到table[0]链表中。如果存在相同的key则将新value覆盖旧value,并将旧的value返回。
    如果key不为空,先通过可以计算出hash值,根据hash值计算在table中的索引位置,而后遍历索引位置对应的链表,如果链表存在与当前key相同的键值对,则将新的value覆盖旧的value,并将旧的value返回,如果找不到与目标key相等的键值对,或者链表为空则添加到该链表的开始位置;
    通过上面的createEntry方法可以看到,将新创建的Entry放到头部,将之前的头部接到它的后面。也就是说明,每次put键值对的时候,总是将新的键值对放到头部

    • 注意containsKeycontainsValue方法,前者可以通过hash值定位到链表,而后者则需要对整个hash数组的所有链表进行扫描
    • 为什么哈希表的容量一定要是2的整数次幂。
      首先,length为2的整数次幂的话,h&(length-1) 就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率。其次,length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性,而如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间,因此,length取2的整数次幂,是为了使不同hash值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。
文章目录
  1. 1. HashMap简介
  2. 2. 运行环境
  3. 3. 源码分析
  4. 4. 几点总结