HashMap 是咱們在項目中經常使用的集合類,可是對於它的底層實現咱們卻不甚瞭解,例如:node
1. HashMap 的內部數據結構是什麼?初始容量是多少?算法
2. HashMap 的 put 方法的過程?bootstrap
3. HashMap 的 hash 函數是如何實現的?如何解決 hash 衝突?數組
4. HashMap 哪時擴容?如何擴容?數據結構
......app
本文將分析 JDK8 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)時,鏈表將會轉成 紅黑樹 結構,後續內容中會有詳細分析。
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 衝突了:
若節點已經存在就替換舊值
4. 判斷元素個數是否 >= threshold(擴容閾值),若是達到就須要 resize,見代碼 101 行.
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 行。
當 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 的源碼分析暫時到這裏結束,若是有哪些地方不清楚的話請在下方留言,本人後續會繼續更新。