Java HashMap類源碼解析

作為重要的常用集合,HashMap主要是提供鍵值對的存取,通過key值可以快速找到對應的value值。Hash表是通過提前設定好的規則計算一個元素的hash值來找到他在數組中的存儲位置進行快速定位,假設有一個大小為10的數組,可以設定簡單的計算規則為元素轉為int後mod 10,由此元素的hash值一定會落在大小為10的數組內。由於不同元素可能會計算出相同的hash值,如例子中1和11都應該在下標為1的位置,這就是hash值的衝突。為了解決這個問題有幾種常用的策略:

  1. 鏈表法,先加入11存儲在A[1]的位置,然後加入1,檢查A[1]已經有數了,將1連接到11的後面形成鏈表。
  2. 開放地址法,檢查到衝突後根據一定規則去檢查另外的位置是否有空可以存儲新的元素。根據探測方法的不同,常見的有線性探測法,按照A[1], A[2]…的順序檢查,以及平方探測法,按照A[1], A[1+1^2], A[1+2^2]…
  3. 再hash法,按照另外的計算公式重新計算hash值知道不再衝突。

由此引入一個hash表的屬性—— 負載因子 ,負載因子=存儲的元素個數/數組大小。很顯然,鏈表法由於衝突位置鏈無限延長的特點,若不加以限制負載因子可以超過1,負載因子越大代表表中的數據越密集。

HashMap的key和value值都可以為null,get操作時若找不到對應的key值會返回null,具體見下方的例子:

1 public static void main(String args[]){

2 Map map = new HashMap<>();

3 System.out.println(map.put(null, "123"));//null

4 System.out.println(map.put("456", null));//null

5 System.out.println(map.get("123"));//null

6 System.out.println(map.get(null));//123

7 System.out.println(map.get("456"));//null

8 System.out.println(map.put(null, "345"));//123

9 System.out.println(map.get(null));//345

10 }

View Code

因為篇幅加難度的原因TreeNode部分的分析下次再說了, 爭取不鴿

首先大致翻譯下note的主要內容:通常是桶式hash表(鏈表解決衝突),但是桶過大達到TREEIFY_THRESHOLD值的時候會轉為樹狀TreeNode使得密度過高時的操作可以變得更快。一般對象在樹中是按hashcode排序,但是對於實現了Comparable的對象是通過comapreTo來排序。由於TreeNode的大小接近普通node的兩倍,當桶變小時會轉回線性鏈表。TreeNode是JDK8引入的紅黑樹結構,樹根通常是hash映射的第一個結點,除了Iterator.remove之外。鏈表在樹化或是分裂時保證結點的遍歷順序是一致的。

1 static class Node<K,V> implements Map.Entry<K,V> {

2 final int hash;

3 final K key;

4 V value;

5 Node next;

6

7 Node(int hash, K key, V value, Node next) {

8

this.hash = hash;

9 this.key = key;

10 this.value = value;

11 this.next = next;

12 }

13

14 public final K getKey() { return key; }

15 public final V getValue() { return value; }

16 public final String toString() { return key + "=" + value; }

17

18 public final int hashCode() {

19 //key和value的hashCode亦或

20 return Objects.hashCode(key) ^ Objects.hashCode(value);

21 }

22

23 public final V setValue(V newValue) {

24 V oldValue = value;

25 value = newValue;

26 return oldValue;

27 }

28

29 public final boolean equals(Object o) {

30 if (o == this)

31 return true;

32 if (o instanceof Map.Entry) {

33 Map.Entry,?> e = (Map.Entry,?>)o;

34 //key == e.getKey()或者key.equals(e.getKey())

35 if (Objects.equals(key, e.getKey()) &&

36 Objects.equals(value, e.getValue()))

37 return true;

38 }

39 return false;

40 }

41 }

View Code

然後我們來看下Node的結構。一般的箱式結點實現了Map.Entry接口,這是一個key-value鍵值對,內部有4個屬性hash值、K、V以及指向下個結點的引用。hashCode方法是對key和value的hash值求異或,也重寫了equals和toString方法。

1 static final int hash(Object key) {

2 int h;

3 //key的hashCode無符號右移16位的值與自己進行異或

4 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

5 }

View Code

對於HashMap自身的hash方法,這樣做的目的是避免hash值高位因為表大小而永遠不會被用於hash值的計算,使得分佈可以更加均勻。

1 static final int tableSizeFor(int

cap) {

2 int n = cap - 1;//cap=0001 1000 0001 1111(6175) n = 0001 1000 0001 1110

3 n |= n >>> 1;//n = 0001 1100 0001 1111

4 n |= n >>> 2;//n = 0001 1111 0001 1111

5 n |= n >>> 4;//n = 0001 1111 1111 1111

6 n |= n >>> 8;//n = 0001 1111 1111 1111

7 n |= n >>> 16;//n = 0001 1111 1111 1111(8191)

8 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

9 }

View Code

根據cap返回恰好大於等於該值的2的指數大小。以cap = 6175進行演示,可得n最終為8191即2^13-1,所以返回值為2^13

HashMap內部屬性如下:

//hash表的底層數組,大小永遠是2的指數

transient Node[] table;

//含有鍵值對的set高速緩存

transient Set

> entrySet;

//鍵值對的數量

transient int size;

//HashMap被結構性修改的次數,包括改變鍵值對個數的操作和rehash等改變內部結構的操作,用於迭代器在線程不安全時快速拋錯

transient int modCount;

//達到某個大小後就要改變數組大小,等於capacity * load factor

int threshold;

//負載因子

final float loadFactor;

View Code

構造函數:

1 //參數缺省值為16和0.75

2 public HashMap(int initialCapacity, float loadFactor) {

3 if (initialCapacity < 0)

4 throw new IllegalArgumentException("Illegal initial capacity: " +

5 initialCapacity);

6 //大小最大不能超過1<<30

7 if (initialCapacity > MAXIMUM_CAPACITY)

8 initialCapacity = MAXIMUM_CAPACITY;

9 if (loadFactor <= 0 || Float.isNaN(loadFactor))

10 throw new IllegalArgumentException("Illegal load factor: " +

11 loadFactor);

12 this.loadFactor = loadFactor;

13 //計算恰好大於等於initialCapacity的2的指數作為容量大小

14 this.threshold = tableSizeFor(initialCapacity);

15 }

16 public HashMap(Map extends K, ? extends V> m) {

17 this.loadFactor = DEFAULT_LOAD_FACTOR;

18 putMapEntries(m, false);

19 }

View Code

這裡通過一個已有Map來構造時用到了putMapEntries這個方法,先來看下這個方法

1 final void putMapEntries(Map extends K, ? extends V> m, boolean evict) {

2 int s = m.size();

3 if (s > 0) {

4

if (table == null) { //當前沒有元素

5 float ft = ((float)s / loadFactor) + 1.0F;//計算出m的容量

6 int t = ((ft < (float)MAXIMUM_CAPACITY) ?

7 (int)ft : MAXIMUM_CAPACITY);

8 if (t > threshold)

9 threshold = tableSizeFor(t);

10 }

11 else if (s > threshold)

12 resize();//若m的元素個數超過了threhold則需要擴展表

13 for (Map.Entry extends K, ? extends V> e : m.entrySet()) {

14 K key = e.getKey();

15 V value = e.getValue();

16 putVal(hash(key), key, value, false, evict);//將鍵值對插入到表中

17 }

18 }

19 }

View Code

可以看到其中調用了用於擴展表的resize和插入鍵值對的putVal。先看resize方法,這個方法在表大小超過threhold時就會被調用,作用就是擴展數組大小,並將元素複製到數組中,同時對於衝突鏈表不需要重新計算hash值而是會根據他們的hash值決定要不要複製到數組的高位去

1 final Node[] resize() {

2 Node[] oldTab = table;

3 int oldCap = (oldTab == null) ? 0 : oldTab.length;//當前表中元素個數

4 int oldThr = threshold;

5 int newCap, newThr = 0;

6 if (oldCap > 0) {

7 if (oldCap >= MAXIMUM_CAPACITY) {

8 threshold = Integer.MAX_VALUE;

9 return oldTab;

10 }

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

12 oldCap >= DEFAULT_INITIAL_CAPACITY)

13 newThr = oldThr << 1; // 當前表中元素個數大於等於16且小於上限的一半時,threshold加倍

14 }

15 else if (oldThr > 0) // 這個條件成立時說明構造時給了capacity參數,由此計算出了threhold

16 newCap = oldThr;

17 else { //沒有任何參數的初始化直接使用默認值

18 newCap = DEFAULT_INITIAL_CAPACITY;

19 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

20 }

21 if (newThr == 0) {//當前表中沒有元素的情況

22 float ft = (float)newCap * loadFactor;

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

24 (int)ft : Integer.MAX_VALUE);

25 }

26 threshold = newThr;

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

28 Node[] newTab = (Node[])new Node[newCap];

29 table = newTab;

30 if (oldTab != null) {

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

32 Node e;

33 if ((e = oldTab[j]) !=

null) {

34 oldTab[j] = null;

35 if (e.next == null)

36 //e沒有後續鏈表結點時,因為newCap是oldCap的2倍,相當於掩碼多了一位,原本hash值的這個多出來的有效位是0或1會決定它在新數組中下標是否變化

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

38 else if (e instanceof TreeNode)

39 ((TreeNode)e).split(this, newTab, j, oldCap);

40 else { // 存在非樹的鏈表時,保持先後順序不變

41 Node loHead = null, loTail = null;//低位鏈表

42 Node hiHead = null, hiTail = null;//高位鏈表

43 Node next;

44 do {

45 next = e.next;

46 //這裡的運算相當於直接檢查hash新增的高位是0還是1,因為oldCap是2的指數所以只有最高位是1其餘都是0,舊hash的高位為1時要進行移動

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

48 if (loTail == null)

49 loHead = e;

50 else

51 loTail.

next = e;

52 loTail = e;

53 }

54 else {

55 if (hiTail == null)

56 hiHead = e;

57 else

58 hiTail.next = e;

59 hiTail = e;

60 }

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

62 if (loTail != null) {

63 loTail.next = null;

64 newTab[j] = loHead;//低位鏈表直接複製到原本所在的位置

65 }

66 if (hiTail != null) {

67 hiTail.next = null;

68 newTab[j + oldCap] = hiHead;//高位鏈表的移動規則是原本的下標+oldCap

69 }

70 }

71 }

72 }

73 }

74 return newTab;

75 }

View Code

putVal這個方法是把值存入表中,在多個put類方法中被調用

1 /**

2 * Implements Map.put and related methods

3 *

4 * @param hash hash for key

5 * @param key the key

6 * @param value the value to put

7 * @param onlyIfAbsent if true, don't change existing value為true表示不改變已有值

8 * @param evict if false, the table is in creation mode.為false表示是新建表

9 * @return previous value, or null if none

10 */

11 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

12 boolean evict) {

13 Node[] tab; Node p; int n, i;

14 if ((tab = table) == null || (n = tab.length) == 0)

15 n = (tab = resize()).length;//當前表的table數組為空時需進行擴展

16 if ((p = tab[i = (n - 1) & hash]) == null)//hash值截斷到n-1對應的位數進行定位

17 tab[i] = newNode(hash, key, value, null);//若該下標位置為空,則直接放入數組

18 else {

19 Node e; K k;

20 if (p.hash == hash &&

21 ((k = p.key) == key || (key != null && key.equals(k))))

22 e = p;//檢查表上的根結點的hash與key值是否與新增的結點相等,若相等則將修改根結點的value

23 else if (p instanceof

TreeNode)

24 e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);//已經是樹調用樹的遍歷方法

25 else {

26 for (int binCount = 0; ; ++binCount) {

27 if ((e = p.next) == null) {

28 p.next = newNode(hash, key, value, null);//若在箱式鏈表中沒有找到key相等的結點,則新建結點插入到鏈表末尾

29 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st

30 treeifyBin(tab, hash);//若增加該結點後,鏈表上的結點數超過了TREEIFY_THRESHOLD則轉為樹,該判斷僅在遍歷到鏈表末尾時執行

31 break;

32 }

33 if (e.hash == hash &&

34 ((k = e.key) == key || (key != null && key.equals(k))))//找到了key和hash值相等的結點

35 break;

36 p = e;

37 }

38 }

39 if (e != null) { // 找到了相同的key則修改value值並返回舊的value

40 V oldValue = e.value;

41 if (!onlyIfAbsent || oldValue == null)

42 e.value = value;

43 afterNodeAccess(e);

44 return oldValue;

45 }

46 }

47 ++modCount;//新增結點時增加modCount

48 if (++size > threshold)

49 resize();//大小超過threshold時要擴容

50 afterNodeInsertion(evict);//這個方法是用於繼承了HashMap的LinkedHashMap,用來移除最早放入的結點,保持插入的順序,為false時代表是新建表不需要進行這個過程

51 return null;

52 }

View Code

treeifyBin將指定hash值對應的位置上的鏈表替換為樹,除非整個表的大小太小時調用resize,(n - 1) & hash等效於hash mod n,只保留hash除以n的餘數作為index的值

1 final void treeifyBin(Node[] tab, int hash) {

2 int n, index; Node e;

3 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)

4 resize();//表長度小於MIN_TREEIFY_CAPACITY調用resize

5 else if ((e = tab[index = (n - 1) & hash]) != null) {//該hash值對應的index位置上有元素

6 TreeNode hd = null, tl = null;

7 do {

8 TreeNode p = replacementTreeNode(e, null);//將鏈表轉換為樹

9 if (tl == null)

10 hd = p;

11 else {

12 p.prev = tl;

13 tl.next = p;

14 }

15 tl = p;

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

17 if ((tab[index] = hd) != null)

18 hd.treeify(tab);//樹放入index的位置

19 }

20 }

View Code

對於移除操作會調用removeNode,這個方法在多個移除方法中被使用。若移除指定key值成功的結點會返回value值,否則返回null

1 public V remove(Object key) {

2 Node e;

3 return (e = removeNode(hash(key), key, null, false,

true)) == null ?

4 null : e.value;

5 }

6 /**

7 * 用於移除操作

8 *

9 * @param hash hash for key

10 * @param key the key

11 * @param value 僅matchValue為true時需要考慮,其他時間不起效

12 * @param matchValue 為true時只移除value相等的

13 * @param movable 為false時不移動其他結點

14 * @return the node, or null if none

15 */

16 final Node

removeNode(int hash, Object key, Object value,

17 boolean matchValue, boolean movable) {

18 Node[] tab; Node p; int n, index;

19 if ((tab = table) != null && (n = tab.length) > 0 &&

20 (p = tab[index = (n - 1) & hash]) != null) {//表不為空且hash值對應的index位置存在元素

21 Node node = null, e; K k; V v;

22 if (p.hash == hash &&

23 ((k = p.key) == key || (key != null && key.equals(k))))//根結點的key值相等

24 node = p;

25 else

if ((e = p.next) != null) {//根結點key值不相等,存在後續結點

26 if (p instanceof TreeNode)

27 node = ((TreeNode)p).getTreeNode(hash, key);//調用樹的遍歷方法尋找結點

28 else {

29 do {

30 if (e.hash == hash &&//為箱式鏈表時遍歷鏈表尋找key相等的點

31 ((k = e.key) == key ||

32 (key != null && key.equals(k)))) {

33 node = e;

34 break;

35 }

36 p = e;

37 } while ((e = e.next) != null

);

38 }

39 }

40 if (node != null && (!matchValue || (v = node.value) == value ||

41 (value != null && value.equals(v)))) {//matchValue為true還需要驗證value是否相等,否則忽略

42 if (node instanceof TreeNode)

43 ((TreeNode)node).removeTreeNode(this, tab, movable);//移除樹結點

44 else if (node == p)

45 tab[index] = node.next;//為箱式鏈表根結點時,將第二個結點放到數組上

46 else

47 p.next = node.next;//為箱式鏈表非結點時,修改上下結點間的指針

48 ++modCount;//增加modCount

49 --size;

50 afterNodeRemoval(node);//預留給LinkedHashMap的方法

51 return node;

52 }

53 }

54 return null;

55 }

View Code

clear方法不難理解,將表內所有元素設為null,size變為0,增加modCount

1 public void clear() {

2 Node[] tab;

3 modCount++;

4 if ((tab = table) != null && size > 0) {

5 size

= 0;

6 for (int i = 0; i < tab.length; ++i)

7 tab[i] = null;

8 }

9 }

View Code

尋找表內有無相等的value,遍歷整個鏈表找到則返回true

1 public boolean containsValue(Object value) {

2 Node[] tab; V v;

3 if ((tab = table) != null && size > 0) {

4 for (int i = 0; i < tab.length; ++i) {//遍歷hash表

5 for (Node e = tab[i]; e != null; e = e.next) {//遍歷鏈表

6 if ((v = e.value) == value ||

7 (value != null && value.equals(v)))

8 return true;

9 }

10 }

11 }

12 return false;

13 }

View Code

key是一個set集合,value是一個collection集合,調用keySet()和values()返回的集合是對HashMap中key和value的直接引用,所以操作會直接反應在HashMap上

1 public Set keySet() {

2 Set ks = keySet;

3 if (ks == null) {

4 ks = new KeySet();

5 keySet = ks;

6 }

7 return ks;

8 }

9

10 final class KeySet extends AbstractSet<K> {

11 public final int size() { return

size; }

12 public final void clear() { HashMap.this.clear(); }//調用的是HashMap.clear(),所以整個表會被清空

13 public final Iterator iterator() { return new KeyIterator(); }

14 public final boolean contains(Object o) { return containsKey(o); }

15 public final boolean remove(Object key) {

16 return removeNode(hash(key), key, null, false, true) != null;

17 }

18 public final Spliterator spliterator() {

19 return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);

20 }

21 public final void forEach(Consumer super K> action) {

22 Node[] tab;

23 if (action == null)

24 throw new NullPointerException();

25 if (size > 0 && (tab = table) != null) {

26 int mc = modCount;

27 for (int i = 0; i < tab.length; ++i) {

28 for (Node e = tab[i]; e != null; e = e.next)

29 action.accept(e.key);

30 }

31 if (modCount != mc)//遍歷迭代器要求不能被其他線程修改表內元素個數而引起modCount變化

32 throw

new ConcurrentModificationException();

33 }

34 }

35 }

View Code

根據上面對putVal的分析,該方法不會改變已有的key值,返回值為舊值或null

1 public V putIfAbsent(K key, V value) {

2 return putVal(hash(key), key, value, true, true);

3 }

View Code

然後來看一下兩個replace方法,區別在於返回值和是否檢查value值

1 @Override

2 public boolean replace(K key, V oldValue, V newValue) {

3 Node e; V v;

4 if ((e = getNode(hash(key), key)) != null &&

5 ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {//key和value要同時符合條件

6 e.value = newValue;

7 afterNodeAccess(e);//也是用於LinkedHashMap保持結點插入順序用的

8 return true;

9 }

10 return false;

11 }

12

13 @Override

14 public V replace(K key, V value) {

15 Node e;

16 if ((e = getNode(hash(key), key)) != null) {//僅key符合條件

17 V oldValue = e.value;

18 e.value = value;

19 afterNodeAccess(e);

20 return oldValue;

21 }

22 return null;

23 }

View Code

computeIfAbsent這個方法的作用是若key值在map中已有非null的value值,則直接返回舊value值;若value值為null則根據mappingFunction計算出新的value值並修改map中存在的鍵值對,返回新value值;若不存在key值則新增一個鍵值對插入到key的hash值對應的table數組位置鏈表的頭部,並返回新的value值。 注意, putVal 方法插入的結點是在鏈表尾部,而該方法是在鏈表頭部。

1 public V computeIfAbsent(K key,

2 Function super K, ? extends V> mappingFunction) {

3 if (mappingFunction == null)

4 throw new NullPointerException();

5 int hash = hash(key);

6 Node[] tab; Node first; int n, i;

7 int binCount = 0;

8 TreeNode t = null;

9 Node old = null;

10 if (size > threshold || (tab = table) == null ||

11 (n = tab.length) == 0)

12 n = (tab = resize()).length;//table空間不足時擴展數組

13 if ((first = tab[i = (n - 1) & hash]) != null) {//hash值對應的下標在table內不為空

14 if (first instanceof TreeNode)

15 old = (t = (TreeNode)first).getTreeNode(hash, key);

16 else {

17 Node e = first; K k;

18 do {

19 if (e.hash == hash &&

20 ((k = e.key) == key || (key != null

&& key.equals(k)))) {//對箱式鏈表搜索key相等的結點

21 old = e;

22 break;

23 }

24 ++binCount;

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

26 }

27 V oldValue;

28 if (old != null && (oldValue = old.value) != null) {//找到了key相等的結點且value不為null

29 afterNodeAccess(old);//LinkedHashMap方法

30 return oldValue;//返回舊value值

31 }

32 }

33 V v = mappingFunction.apply(key);//根據function計算出新的v值

34 if (v ==

null) {

35 return null;//新的v值為null則直接返回

36 } else if (old != null) {//找到了key相等的結點且value為null,賦予新v值後返回新v值

37 old.value = v;

38 afterNodeAccess(old);

39 return v;

40 }

41 else if (t != null)

42 t.putTreeVal(this, tab, hash, key, v);//樹結點處理

43 else {

44 tab[i] = newNode(hash, key, v, first);//沒有找到key相等的結點,新建一個結點並且插入到鏈表頭部

45 if (binCount >= TREEIFY_THRESHOLD - 1)

46 treeifyBin(tab, hash);//若新增結點後鏈表長度達到了TREEIFY_THRESHOLD則轉為樹

47 }

48 ++modCount;//該部分僅新增結點時執行

49 ++size;

50 afterNodeInsertion(true);

51 return v;

52 }

View Code

然後是兩個相近的方法:computeIfPresent存在key相等且value不為null的結點,計算新的value值,新value不為null則覆蓋,新value為null則移除原本的結點。compute方法結合了前兩者,存在key相等的結點時不考慮舊value值,新value為null則移除,不為null則覆蓋value值;不存在key相等的結點時,新value值不為null則新增結點,對箱式鏈表插入到鏈表頭部,插入後要檢車是否需要轉為樹。

1 public V computeIfPresent(K key,

2 BiFunction

super K, ? super V, ? extends V> remappingFunction) {

3 if (remappingFunction == null)

4 throw new NullPointerException();

5 Node e; V oldValue;

6 int hash = hash(key);

7 if ((e = getNode(hash, key)) != null &&

8 (oldValue = e.value) != null) {//存在key相等且value不為null的結點

9 V v = remappingFunction.apply(key, oldValue);

10 if (v != null) {

11 e.value = v;//新value值不為null則修改value值

12 afterNodeAccess(e);

13 return v;

14 }

15 else

16 removeNode(hash, key, null, false, true);//新value值為null則移除這個結點

17 }

18 return null;

19 }

20

21 @Override

22 public V compute(K key,

23 BiFunction super K, ? super V, ? extends V> remappingFunction) {

24 if (remappingFunction == null)

25 throw new NullPointerException();

26 int hash = hash(key);

27 Node[] tab; Node first; int n, i;

28 int binCount = 0;

29 TreeNode t = null;

30 Node old = null;

31 if (size > threshold || (tab = table) == null ||

32 (n = tab.length) == 0)

33 n = (tab = resize()).length;//table空間不足時調用resize

34 if ((first = tab[i = (n - 1) & hash]) !=

null) {

35 if (first instanceof TreeNode)

36 old = (t = (TreeNode)first).getTreeNode(hash, key);//樹中尋找key相等的結點

37 else {

38 Node e = first; K k;

39 do {

40 if (e.hash == hash &&

41 ((k = e.key) == key || (key != null && key.equals(k)))) {

42 old = e;//找到了key相等的結點

43 break;

44 }

45 ++binCount;

46 } while ((e = e.next) != null

);

47 }

48 }

49 V oldValue = (old == null) ? null : old.value;

50 V v = remappingFunction.apply(key, oldValue);

51 if (old != null) {

52 if (v != null) {

53 old.value = v;//找到了key值相等的結點且新value不為null則舊結點的value設為新值

54 afterNodeAccess(old);

55 }

56 else

57 removeNode(hash, key, null, false, true);//找到了key值相等的結點且新value為null則移除舊結點

58 }

59 else if (v != null) {//沒有找到key值相等的結點且新value值不為null

60 if (t != null)

61 t.putTreeVal(this, tab, hash, key, v);//樹中插入新結點

62 else {

63 tab[i] = newNode(hash, key, v, first);//新建結點插入到鏈表頭部

64 if (binCount >= TREEIFY_THRESHOLD - 1)

65 treeifyBin(tab, hash);//新增後鏈表長度達到TREEIFY_THRESHOLD則轉為樹

66 }

67 ++modCount;//僅新增結點時執行

68 ++size;

69 afterNodeInsertion(true);

70 }

71

return v;

72 }

View Code

merge這個方法和前面差不多,也是先尋找key值相同的結點,若存在則看該結點value是否為null,不為null根據function和參數中的value以及結點原本的value計算出新的value值,否則直接賦予參數中的value值。若沒有找到結點,則按照參數中key和value值新建一個結點插入到樹中或者箱式鏈表的頭部。

1 public V merge(K key, V value,

2 BiFunction super V, ? super V, ? extends V> remappingFunction) {

3 if (value == null)

4 throw new NullPointerException();

5 if (remappingFunction == null)

6 throw

new NullPointerException();

7 int hash = hash(key);

8 Node[] tab; Node first; int n, i;

9 int binCount = 0;

10 TreeNode t = null;

11 Node old = null;

12 if (size > threshold || (tab = table) == null ||

13 (n = tab.length) == 0)

14 n = (tab = resize()).length;//表空間不足時調用resize

15 if ((first = tab[i = (n - 1) & hash]) != null) {

16 if (first instanceof TreeNode)

17 old = (t = (TreeNode)first).getTreeNode(hash, key);//尋找樹中key值相等的結點

18 else

{

19 Node e = first; K k;

20 do {

21 if (e.hash == hash &&

22 ((k = e.key) == key || (key != null && key.equals(k)))) {

23 old = e;//尋找箱式鏈表中key值相等的結點

24 break;

25 }

26 ++binCount;

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

28 }

29 }

30 if (old != null) {//尋找到key值相等的結點

31 V v;

32 if (old.value != null)//舊值不為null

33 v = remappingFunction.apply(old.value, value);//根據remappingFunction和舊value和參數中的value計算出新的value值

34 else

35 v = value;//舊值為null則新value=參數中的value

36 if (v != null) {

37 old.value = v;//計算出的新value值不為null則覆蓋尋找到結點的value值

38 afterNodeAccess(old);

39 }

40 else

41 removeNode(hash, key, null, false, true);//計算出的新value值為null則移除找到的結點

42 return v;

43 }

44 if (value != null) {//沒有尋找到key相等的結點

45 if (t != null)

46 t.putTreeVal(this, tab, hash, key, value);//樹中新增結點

47 else {

48 tab[i] = newNode(hash, key, value, first);//新建結點插入到鏈表頭部

49 if (binCount >= TREEIFY_THRESHOLD - 1)

50 treeifyBin(tab, hash);//新增結點後鏈表長度達到TREEIFY_THRESHOLD則轉為樹

51 }

52 ++modCount;//新增結點後執行

53 ++size;

54 afterNodeInsertion(true);

55 }

56 return value;

57 }

View Code

兩個批量操作不難理解,同樣要保證過程中沒有其他線程修改了對象的元素個數

1 public void forEach(BiConsumer super K, ? super V> action) {

2 Node[] tab;

3 if (action == null)

4 throw new NullPointerException();

5 if (size > 0 && (tab = table) != null) {

6 int mc = modCount;

7 for (int i = 0; i < tab.length; ++i) {

8 for (Node e = tab[i]; e !=

null; e = e.next)

9 action.accept(e.key, e.value);//對每個結點執行對應的操作

10 }

11 if (modCount != mc)

12 throw new ConcurrentModificationException();//有其他線程修改了HashMap中的元素個數時拋錯

13 }

14 }

15 public void replaceAll(BiFunction super K, ? super V, ? extends V> function) {

16 Node[] tab;

17 if (function == null)

18 throw

new NullPointerException();

19 if (size > 0 && (tab = table) != null) {

20 int mc = modCount;

21 for (int i = 0; i < tab.length; ++i) {

22 for (Node e = tab[i]; e != null; e = e.next) {

23 e.value = function.apply(e.key, e.value);

24 }

25 }

26 if (modCount != mc)

27 throw new ConcurrentModificationException();

28 }

29 }

View Code

HashMap實現了clone方法,可以產生一個新的完全一樣的HashMap

1 public Object clone() {

2 HashMap result;

3 try {

4 result = (HashMap)super.clone();//產生一個複製HashMap

5 } catch (CloneNotSupportedException e) {

6 // 因為HashMap支持clone方法,應該不會拋出這個錯誤

7 throw new InternalError(e);

8 }

9 result.reinitialize();//初始化參數值,所有集合數組都設為null

10 result.putMapEntries(this, false);//將原本集合中的鍵值對都插入到新產生的Map中

11 return result;

12 }

View Code

capacity這個方法先看table是否為null,不為null直接返回table.length。然後看threshold是否為0,不為0返回threshold否則返回默認容量16

1 final int capacity() {

2 return (table != null) ? table.length :

3 (threshold > 0) ? threshold :

4 DEFAULT_INITIAL_CAPACITY;

5 }

View Code

HashMap的序列化方法同樣利用的是ObjectOutputStream

1 private void writeObject(java.io.ObjectOutputStream s)

2

throws IOException {

3 int buckets = capacity();

4 // 先寫入數組大小和集合內元素的個數

5 s.defaultWriteObject();

6 s.writeInt(buckets);

7 s.writeInt(size);

8 internalWriteEntries(s);

9 }

10 // 只有writeObject會調用這個方法

11 void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {

12 Node[] tab;

13 if (size > 0 && (tab = table) != null) {

14 for (int i = 0; i < tab.length; ++i) {//遍歷整個數組,按照鏈表順序寫入key和value值

15 for (Node e = tab[i]; e != null; e = e.next) {

16 s.writeObject(e.key);

17 s.writeObject(e.value);

18 }

19 }

20 }

21 }

View Code

序列化輸入是通過ObjectInputStreams,loadFactor一定會限制在0.25-4.0之間,而threshold是根據size/loadFactor + 1.0然後計算出大於等於該值的最小2的指數次冪。

1 private void readObject(java.io.ObjectInputStream s)

2 throws IOException, ClassNotFoundException {

3 // Read in the threshold (ignored), loadfactor, and any hidden stuff

4 s.defaultReadObject();

5 reinitialize();

6

if (loadFactor <= 0 || Float.isNaN(loadFactor))

7 throw new InvalidObjectException("Illegal load factor: " +

8 loadFactor);

9 s.readInt(); // 讀取數組大小

10 int mappings = s.readInt(); // 讀取元素個數

11 if (mappings < 0)

12 throw new InvalidObjectException("Illegal mappings count: " +

13 mappings);

14 else if (mappings > 0) { // (if zero, use defaults)

15 // loadFactor一定在0.25-4.0之間

16 float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);

17 float fc = (float)mappings / lf + 1.0f;

18 int

cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?

19 DEFAULT_INITIAL_CAPACITY :

20 (fc >= MAXIMUM_CAPACITY) ?

21 MAXIMUM_CAPACITY :

22 tableSizeFor((int)fc));//threshold的值在不超過範圍的情況下設定為恰好大於等於size/loadFactor + 1的2的指數

23 float ft = (float)cap * lf;

24 threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?

25 (int)ft : Integer.MAX_VALUE);

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

27 Node[] tab = (Node[])new Node[cap];

28 table = tab;

29

30 // Read the keys and values, and put the mappings in the HashMap

31 for (int i = 0; i < mappings; i++) {

32 @SuppressWarnings("unchecked")

33 K key = (K) s.readObject();

34 @SuppressWarnings("unchecked")

35 V value = (V) s.readObject();

36 putVal(hash(key), key, value, false, false);//從輸入流中得去key和value值並通過putVal插入

37 }

38 }

39 }

View Code


分享到:


相關文章: