HashMap的put kv,是如何擴容的?
阿新 • • 發佈:2020-11-03
HashMap的put kv,是如何擴容的?
描述下HashMap put(k,v)的流程?
它的擴容流程是怎麼樣的?
HashMap put(k,v)流程
- 通過hash(key方法)獲取到key的hash值
- 呼叫put方法, 將value存放到指定的位置
- 根據hash值確定當前key所在node陣列的索引
(n - 1) & hash
- 如果node[i]==null 則直接建立新陣列
- 如果node[i]!=null
- 判斷 當前node的頭結點的 hash和key是否都相等, 相等則需要操作的就是該node
- 判斷當前節點是否為TreeNode,對TreeNode進行操作,並返回結果e
- 如果是連結串列則遍歷連結串列,key存在則返回節點e,不存在則賦值
- 判斷節點e有沒有被賦值,覆蓋舊值
- hashMap size進行加1,同時判斷v新size是否大於擴容閾值從而判斷是否需要擴容
- 根據hash值確定當前key所在node陣列的索引
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { // 宣告Node陣列tab, Node節點 Node<K,V>[] tab; Node<K,V> p; int n, i; // 對tab陣列賦值為當前HashMap的table, 並判斷是否為空, 或者長度為0 // 為0進行則resize()陣列, 並對 n賦值為當前tab的長度 // resize() 對HashMap的table擴容, 並返回擴容後的新陣列 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 對 node p 進行賦值, 陣列所在位置 即 node p 如果是null 則直接賦值 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { // p 不為null, 宣告 node e, key k Node<K,V> e; K k; // 如果hash值相等且key相等, 直接將 e 賦值為當前node的頭節點 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) // 如果是紅黑樹, 則對樹進行操作, 返回節點e e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { // 對連結串列進行遍歷, 找到對應的節點 for (int binCount = 0; ; ++binCount) { // 將 e 賦值為 頭節點p的next, 如果下一個節點為null if ((e = p.next) == null) { // 對節點進行賦值 p.next = newNode(hash, key, value, null); // 如果長度到達數轉換閾值, 則需要轉換為紅黑樹 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } // 如果e節點的hash相等, key相等, 則 直接跳出迴圈 e 已經被賦值為 p.next // 此時e節點的value沒有被賦值 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; // 指標指向下一個節點, 繼續遍歷 p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; // 對舊值進行覆蓋, 並返回舊值 if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; // 是否需要擴容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
resize()擴容過程
- JDK 1.7 擴容流程, 每次都需要陣列擴容後, 連結串列需要重新計算在新陣列的位置
- JDK 1.8 不需要重新計算 (優化點)
- 陣列下標: (n - 1) & hash 即陣列長度-1 & key的hash
- 擴容後的陣列下標: ((n << 1) - 1) & hash 相當於在 高位1之前加了個1
如圖所示, 真正發生影響的是新增的那一位(紅色箭頭所指), 所以 oldCap & hash 完全可以判斷該值是放在舊索引值的位置還是放在舊索引值+舊陣列長度的位置
final Node<K,V>[] resize() { // 舊陣列 Node<K,V>[] oldTab = table; // 舊陣列長度 int oldCap = (oldTab == null) ? 0 : oldTab.length; // 舊的擴容閾值 int oldThr = threshold; // 新的陣列長度和新擴容閾值 int newCap, newThr = 0; // 舊陣列存在 if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 新陣列長度為舊陣列長度的2倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) // 擴容閾值是舊擴容閾值的2倍 newThr = oldThr << 1; // double threshold } // 舊陣列不存在, 相當於首次put(K, V)時, 將陣列長度置為擴容閾值 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults // 舊陣列不存在, new HashMap()未指定長度, 初次put(K, V), 設定為預設值 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 新的擴容閾值是0, 則將擴容閾值設定為 新陣列長度*負載因子 if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } // 對全域性的擴容閾值進行賦值 threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) // 建立新陣列, 長度為新長度, 即原陣列長度的2倍 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // 將table複製為新陣列 table = newTab; if (oldTab != null) { // 對舊陣列進行遍歷 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; // 舊節點node賦值 if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) // 只有頭結點, 直接計算新的位置並賦值 newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) // 樹單獨處理 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { // next節點 next = e.next; // 節點hash與舊陣列長度 & 的結果來決定元素所在位置, 參考上面圖示所講 if ((e.hash & oldCap) == 0) { // 在元索引出建立新連結串列 if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { // 新索引出建立連結串列 if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; // 索引j處直接賦值 newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; // 索引 j + 老陣列長度位置存放hiHead newTab[j + oldCap] = hiHead; } } } } } return newTab; }