JDK8 HashMap 源碼分析

HashMap 是咱們在項目中經常使用的集合類,可是對於它的底層實現咱們卻不甚瞭解,例如:node

1. HashMap 的內部數據結構是什麼?初始容量是多少?算法

2. HashMap 的 put 方法的過程?bootstrap

3. HashMap 的 hash 函數是如何實現的?如何解決 hash 衝突?數組

4. HashMap 哪時擴容?如何擴容?數據結構

......app

本文將分析 JDK8 HashMap 的源碼,詳細介紹其底層的實現原理和細節。閱讀完本文,你將能夠輕鬆解答以上問題 。事不宜遲,如今讓咱們走進源碼的世界。函數

 

1、HashMap 的數據結構

HashMap 的數據結構在 JDK 8 版本有了很大的改進:源碼分析

JDK 7 及以前:數組 + 鏈表優化

JDK 8 及至今:數組 + 鏈表,鏈表過長時會轉成紅黑樹,紅黑樹節點過少時會退化成鏈表this

 

源碼:

 1 public class HashMap<K,V> extends AbstractMap<K,V>
 2     implements Map<K,V>, Cloneable, Serializable {
 3     /**
 4      * Basic hash bin node, used for most entries.  (See below for
 5      * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
 6      */
 7     static class Node<K,V> implements Map.Entry<K,V> {
 8         final int hash;
 9         final K key;
10         V value;
11         Node<K,V> next; //單項鍊表
12 
13         Node(int hash, K key, V value, Node<K,V> next) {
14             this.hash = hash;
15             this.key = key;
16             this.value = value;
17             this.next = next;
18         }
19 
20         //....
21     }
22 
23     /**
24      * The table, initialized on first use, and resized as
25      * necessary. When allocated, length is always a power of two.
26      * (We also tolerate length zero in some operations to allow
27      * bootstrapping mechanics that are currently not needed.)
28      */
29     transient Node<K,V>[] table;
30 
31 }

源碼分析:

HashMap 的底層實現是一個 Node<K, V> 數組;

數組元素 Node 中有 hash,key,value,next 四個屬性,其中 key 和 value 屬性,符合鍵值對的要求, hash 屬性是 HashMap 內部計算元素存儲下標所用,next 指向一個 Node<K,V> 對象,代表這是個單項鍊表的節點

因此驗證 HashMap 的結構包含有 數組 + 鏈表,至於 紅黑樹,它是 JDK 8 版本爲了提升搜索效率所作的優化,當鏈表的節點數量超過必定數值(JDK 現設置爲 8)時,鏈表將會轉成 紅黑樹 結構,後續內容中會有詳細分析。

 

2、HashMap 的 put 過程

HashMap 的內部結構已經基本瞭解,那麼若是有一個元素到來,它該如何存儲,又該存儲在哪一個位置呢?

接下來咱們來看下 HashMap 中 put 方法的實現:

 

源碼:

  1 public class HashMap<K,V> extends AbstractMap<K,V>
  2     implements Map<K,V>, Cloneable, Serializable {
  3     
  4     /**
  5      * The bin count threshold for using a tree rather than list for a
  6      * bin.  Bins are converted to trees when adding an element to a
  7      * bin with at least this many nodes. The value must be greater
  8      * than 2 and should be at least 8 to mesh with assumptions in
  9      * tree removal about conversion back to plain bins upon
 10      * shrinkage.
 11      */
 12     static final int TREEIFY_THRESHOLD = 8; // 樹狀閾值,當鏈表節點數大於等於該值時,鏈表將會轉換成紅黑樹的結構
 13 
 14     transient Node<K,V>[] table;
 15 
 16     /**
 17      * The number of key-value mappings contained in this map.
 18      */
 19     transient int size; // map 中存儲 Node 的數量
 20 
 21     /**
 22      * The number of times this HashMap has been structurally modified
 23      * Structural modifications are those that change the number of mappings in
 24      * the HashMap or otherwise modify its internal structure (e.g.,
 25      * rehash).  This field is used to make iterators on Collection-views of
 26      * the HashMap fail-fast.  (See ConcurrentModificationException).
 27      */
 28     transient int modCount; // map 修改的次數
 29 
 30     /**
 31      * The next size value at which to resize (capacity * load factor).
 32      * @serial
 33      */
 34     int threshold; // map 的擴容閾值,即 map 中 Node 的個數 = threshold 時,map 將會進行擴容
 35 
 36     public V put(K key, V value) {
 37         return putVal(hash(key), key, value, false, true);
 38     }
 39 
 40     /**
 41      * Implements Map.put and related methods
 42      *
 43      * @param hash hash for key
 44      * @param key the key
 45      * @param value the value to put
 46      * @param onlyIfAbsent if true, don't change existing value
 47      * @param evict if false, the table is in creation mode.
 48      * @return previous value, or null if none
 49      */
 50     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 51                     boolean evict) {
 52         Node<K,V>[] tab; Node<K,V> p; int n, i;
 53         // 判斷數組是否初始化
 54         if ((tab = table) == null || (n = tab.length) == 0)
 55             // 初始化 Node<K,V>[] 數組
 56             n = (tab = resize()).length;
 57         // 計算新增元素在數組的下標位置,若該位置爲 null,則新增 Node
 58         if ((p = tab[i = (n - 1) & hash]) == null)
 59             tab[i] = newNode(hash, key, value, null);
 60         else {
 61             Node<K,V> e; K k;
 62             // 若數組下標位置已存儲 Node 且與新增元素的 key 值相同,則將當前下標元素替換爲新增 Node
 63             if (p.hash == hash &&
 64                 ((k = p.key) == key || (key != null && key.equals(k))))
 65                 e = p;
 66             // 若當前下標 Node 的類型爲樹節點,則交給紅黑樹處理,這裏暫不展開...
 67             else if (p instanceof TreeNode)
 68                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
 69             else {
 70                 // for循環,沿着鏈表往下遍歷
 71                 for (int binCount = 0; ; ++binCount) {
 72                     // 遍歷到鏈表尾部,添加新增 Node
 73                     if ((e = p.next) == null) {
 74                         p.next = newNode(hash, key, value, null);
 75                         // 若是鏈表中 Node 個數 >= 8,則將鏈表轉成紅黑樹(binCount 從0開始,
 76                         // 因此比較時 TREEIFY_THRESHOLD 須要減 1)
 77                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
 78                             treeifyBin(tab, hash);
 79                         break;
 80                     }
 81                     // 若是 key 值相同,跳出循環
 82                     if (e.hash == hash &&
 83                         ((k = e.key) == key || (key != null && key.equals(k))))
 84                         break;
 85                     p = e;
 86                 }
 87             }
 88             // 當前鏈表節點已存在 Node 且 key 值相同
 89             if (e != null) { // existing mapping for key
 90                 V oldValue = e.value;
 91                 if (!onlyIfAbsent || oldValue == null)
 92                     e.value = value;
 93                 afterNodeAccess(e);
 94                 return oldValue;
 95             }
 96         }
 97         // map 修改次數 +1
 98         ++modCount;
 99         // size 表示 Node 的個數,若是size > threshold [ threshold = Node 數組的長度 * 0.75],
100         // 則進行擴容操做
101         if (++size > threshold)
102             resize();
103         afterNodeInsertion(evict);
104         return null;
105     }
106 }

源碼分析:

HashMap 中 put 方法過程:

1. 對 key 求 hash 值,見代碼 37 行;

2. 判斷數組是否初始化,若是未初始化則進行 resize,見代碼 54 行;

3. 計算元素放置位置下標

a. 沒有 hash 衝突,則直接放入數組,若節點已經存在就替換舊值

b. hash 衝突了:

      • 以單向鏈表的方式鏈接到最後,若是鏈表長度超過閥值(TREEIFY_THRESHOLD == 8),就把鏈表轉成紅黑樹
      • 以紅黑樹的方式放入節點

若節點已經存在就替換舊值

4. 判斷元素個數是否  >=  threshold(擴容閾值),若是達到就須要 resize,見代碼 101 行.

 

3、hash 函數

HashMap 內置了 hash 函數對 key-value 鍵值對中的 key 進行了計算,並經過計算結果肯定其存儲位置。

 

源碼:

1 public class HashMap<K,V> extends AbstractMap<K,V>
2     implements Map<K,V>, Cloneable, Serializable {
3 
4     static final int hash(Object key) {
5         int h;
6         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
7     }
8 
9 }

源碼分析:

   HashMap 中 hash 函數實現不單是使用了 key.hashCode() ,還將 hash 值的高 16 bit 和 低 16 bit  作了一個異或,這是 JDK 8 版本的一個優化,使得元素在數組中的落點更加均勻;

  元素存放下標位置:hash(key) & (n - 1),其中 n 爲 HashMap 的容量,見上文 put 源碼 58 行。

 

4、HashMap 的擴容過程

當 HashMap 內的元素到達擴容閾值時,HashMap 將會進行擴容操做。

 

源碼:

  1 public class HashMap<K,V> extends AbstractMap<K,V>
  2     implements Map<K,V>, Cloneable, Serializable {
  3 
  4     transient Node<K,V>[] table;
  5 
  6     static final int hash(Object key) {
  7         int h;
  8         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  9     }
 10 
 11     /**
 12      * The default initial capacity - MUST be a power of two.
 13      */
 14     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
 15 
 16     /**
 17      * The maximum capacity, used if a higher value is implicitly specified
 18      * by either of the constructors with arguments.
 19      * MUST be a power of two <= 1<<30.
 20      */
 21     static final int MAXIMUM_CAPACITY = 1 << 30;
 22 
 23     /**
 24      * Initializes or doubles table size.  If null, allocates in
 25      * accord with initial capacity target held in field threshold.
 26      * Otherwise, because we are using power-of-two expansion, the
 27      * elements from each bin must either stay at same index, or move
 28      * with a power of two offset in the new table.
 29      *
 30      * @return the table
 31      */
 32     final Node<K,V>[] resize() {
 33         Node<K,V>[] oldTab = table;
 34         int oldCap = (oldTab == null) ? 0 : oldTab.length;
 35         int oldThr = threshold;
 36         int newCap, newThr = 0;
 37         // 數組已初始化
 38         if (oldCap > 0) {
 39             // 當前數組容量超出最大限度
 40             if (oldCap >= MAXIMUM_CAPACITY) {
 41                 threshold = Integer.MAX_VALUE;
 42                 return oldTab;
 43             } // 容量擴大一倍,擴容閾值也擴大一倍
 44             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
 45                      oldCap >= DEFAULT_INITIAL_CAPACITY)
 46                 newThr = oldThr << 1; // double threshold
 47         }
 48         else if (oldThr > 0) // initial capacity was placed in threshold
 49             newCap = oldThr;
 50         else {               
 51             // zero initial threshold signifies using defaults
 52             // 數組未初始化時執行
 53             newCap = DEFAULT_INITIAL_CAPACITY; // 容量默認爲 16
 54             // 擴容閾值爲 12
 55             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
 56         }
 57         if (newThr == 0) {
 58             float ft = (float)newCap * loadFactor;
 59             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
 60                       (int)ft : Integer.MAX_VALUE);
 61         }
 62         // 設置擴容閾值 
 63         threshold = newThr;
 64         @SuppressWarnings({"rawtypes","unchecked"})
 65         // 初始化 Node 數組
 66             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
 67         table = newTab;
 68         // 擴容
 69         if (oldTab != null) {
 70             for (int j = 0; j < oldCap; ++j) {
 71                 Node<K,V> e;
 72                 // 當前數組下標不爲空
 73                 if ((e = oldTab[j]) != null) {
 74                     oldTab[j] = null;
 75                     // 當前下標只有一個元素
 76                     if (e.next == null)
 77                         // 將該元素放置到新數組的新下標位置
 78                         newTab[e.hash & (newCap - 1)] = e;
 79                     // 當前下標下是紅黑樹
 80                     else if (e instanceof TreeNode)
 81                         // 將紅黑樹下的元素拆分到新數組
 82                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
 83                     // 當前下標下是鏈表
 84                     else { // preserve order
 85                         Node<K,V> loHead = null, loTail = null;
 86                         Node<K,V> hiHead = null, hiTail = null;
 87                         Node<K,V> next;
 88                         // 遍歷鏈表,將元素遷移到新的數組
 89                         do {
 90                             next = e.next;
 91                             // 判斷遷移後元素在新數組的下標位置,此處爲 HashMap 算法巧妙的地方
 92                             if ((e.hash & oldCap) == 0) {
 93                                 if (loTail == null)
 94                                     loHead = e;
 95                                 else
 96                                     loTail.next = e;
 97                                 loTail = e;
 98                             }
 99                             else {
100                                 if (hiTail == null)
101                                     hiHead = e;
102                                 else
103                                     hiTail.next = e;
104                                 hiTail = e;
105                             }
106                         } while ((e = next) != null);
107                         if (loTail != null) {
108                             loTail.next = null;
109                             newTab[j] = loHead; // 下標位置與舊數組相同
110                         }
111                         if (hiTail != null) {
112                             hiTail.next = null;
113                             newTab[j + oldCap] = hiHead; // 下標位置爲舊下標 + 擴容長度
114                         }
115                     }
116                 }
117             }
118         }
119         return newTab;
120     }
121 
122 }

源碼分析:

HashMap 默認初始化容量爲 16,擴容閾值爲 12,見代碼 53 行;

HashMao 擴容後的容量是以前的一倍,見代碼 44 行;

HashMap 擴容後須要將元素遷移到新的數組,首先會遍歷舊數組(見代碼 70 行),下標元素不爲空時有三種狀況:

    • 只有一個元素;
    • 紅黑樹;
    • 鏈表

具體見代碼 76 - 84 行 

 

 HashMap 的源碼分析暫時到這裏結束,若是有哪些地方不清楚的話請在下方留言,本人後續會繼續更新。

相關文章
相關標籤/搜索