put
方法最經常使用的方法之一,用來向hash桶中添加 鍵值對.可是這個方法並不會去執行實際操做.而是委託putVal
方法進行處理java
public V put(K key, V value) { // 此次個調用分別指定了hash,key,value,替換現有值,非建立模式 return putVal(hash(key), key, value, false, true); }
這裏調用了
hash
方法獲取了key的hash,後面單獨說這個hash的意義node
putVal
方法實際執行put
操做的方法.算法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { // tab - 當前hash桶的引用 // p - key所表明的節點(此節點不必定是目標節點,而僅僅是hash與桶長度的計算值相同而已)(它不爲空時多是鏈表或紅黑樹) // n - 當前桶的容量 // i - key在桶中的下標(同p,不表明目標節點) Node<K,V>[] tab; Node<K,V> p; int n, i; // 初始化局部變量tab並判斷是否爲空,初始化局部變量n並判斷是否爲0 // PS: 源碼中大量的使用了這種書寫方法,不知道放在某寫大廠裏會怎麼樣(斜眼笑) if ((tab = table) == null || (n = tab.length) == 0) // 當tab爲空或n爲0時,代表hash桶還沒有初始化,調用resize()方法,進行初始化並再次初始化局部變量tab與n n = (tab = resize()).length; // 初始化p與i // 這裏使用了(n - 1) & hash的方式計算key在桶中的下標.這個在後面單獨說明 // 當p是否爲空 if ((p = tab[i = (n - 1) & hash]) == null) // p爲空,調用newNode方法初始化節點並賦值到tab對應下標 tab[i] = newNode(hash, key, value, null); else { // p不爲空,發生碰撞.進行後續處理 // e - 目標節點 // k - 目標節點的key Node<K,V> e; K k; // 判斷key是否相同.(這裏除了比較key之外,還比較了hash) // 注意,這裏同時初始化了局部變量k,可是在第二組條件不知足的狀況下,沒有使用價值,能夠被忽略 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) // key相同,將e(目標節點)設置爲p e = p; // 判斷節點是不是紅黑樹 else if (p instanceof TreeNode) // 肯定時,直接委派處理 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { // 走到這裏,表明當前節點爲普通鏈表,進行遍歷查找 // 變量binCount只做爲是否達到tree化的閾值判斷條件. for (int binCount = 0; ; ++binCount) { // 獲取鏈表的下一個元素,並賦值到e(此時e是一箇中間變量,不肯定是不是目標節點) // 第一次for循環時,p表明hash桶中的節點(同時也是鏈表的頭部節點),以後一直等於p.next if ((e = p.next) == null) { // 鏈表遍歷到末尾 // 向鏈表中追加新的節點 p.next = newNode(hash, key, value, null); // 判斷當前鏈表長度是否達到tree閾值 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st // 調用treeifyBin方法直接處理 treeifyBin(tab, hash); // 中斷循環 // 注意,此時局部變量e=null break; } // 能走到此處,說明鏈表未結束,比較e的k是否相同(hash與==) if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) // key相同 break; // e既不爲null也不是目標節點,賦值到p,準備進行下次循環 p = e; } } // 判斷e是否存在 if (e != null) { // existing mapping for key // e不等於null說明操做爲"替換" // 緩存老值 V oldValue = e.value; // 判斷是否必須替換或老值爲null if (!onlyIfAbsent || oldValue == null) // 必須替換或老值爲空,更新節點e的value e.value = value; // 調用回調 afterNodeAccess(e); // 返回老值 // 注意,這裏直接返回了,而沒有進行modCount更新與下面的後續操做 return oldValue; } } // 除了更新鏈表節點之外,都會走到這裏(putTreeVal的返回值是什麼有待確認) // modCount+1 ++modCount; // size+1(元素數量+1) // 判斷是否超過閾值 if (++size > threshold) // 重置大小 resize(); // 調用後置節點插入回調 afterNodeInsertion(evict); return null; }
resize
方法用於添加鍵值對後的擴容與對槽從新分佈的操做數組
final Node<K,V>[] resize() { //----------------------------------- 新容量與閾值計算 ----------------------------------- // 緩存桶引用 Node<K,V>[] oldTab = table; // 緩存老的桶的長度,桶爲null時,使用0 // 注意,這裏用的是oldTab.length,而不是size int oldCap = (oldTab == null) ? 0 : oldTab.length; // 緩存閾值 int oldThr = threshold; // 新桶容量與閾值 int newCap, newThr = 0; // 老容量大於.這通常表明這個桶已經通過了resize的數次處理 if (oldCap > 0) { // 老容量大於MAXIMUM_CAPACITY = 1 << 30 = 1073741824 // 容量計算方式爲n<<1,當oldCap >= MAXIMUM_CAPACITY時,再次執行位移.其可能的最大值就是Integer.MAX_VALUE if (oldCap >= MAXIMUM_CAPACITY) { // 設置閾值爲Integer.MAX_VALUE threshold = Integer.MAX_VALUE; // 直接return.放棄所有後續處理 return oldTab; } // 使用oldCap << 1初始化newCap // 當oldCap小於MAXIMUM_CAPACITY而且oldCap大於DEFAULT_INITIAL_CAPACITY(16)時 // 此時newCap可能已經大於MAXIMUM_CAPACITY而且newThr=0或者newCap很小(小於16>>2)而且newThr=0 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) //設置newThr爲oldThr << 1(這裏沒有作正確性校驗,待查) newThr = oldThr << 1; // double threshold } // 判斷老閾值是否大於0 // 走到這說明oldCap==0,而且使用了包含initialCapacity參數的構造器構造了這個map,且沒有被添加過元素 else if (oldThr > 0) // initial capacity was placed in threshold // 使用將新容量複製爲老閾值(newCap此時爲0) // 注意: 在使用了包含initialCapacity參數的構造方法時,其threshold已經被計算爲2的n次冪 newCap = oldThr; else { // zero initial threshold signifies using defaults // 默認方法,當使用無參構造方法時,會出現oldThr與oldCap都等於0的狀況 // 使用默認初始化容量賦值到newCap newCap = DEFAULT_INITIAL_CAPACITY; // 使用默認初始化容量與加載因子相乘賦值到newThr newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 統一處理newThr if (newThr == 0) { // 新容量與加載因子相乘 float ft = (float)newCap * loadFactor; // 當newCap與ft均小於MAXIMUM_CAPACITY時,newThr=ft.不然newThr=Integer.MAX_VALUE newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } //----------------------------------- 元素重排 ----------------------------------- // 更新threshold threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) // 更新桶對象(此時是空的) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; // 判斷老桶是否爲空 if (oldTab != null) { // 老桶不爲空.進行遍歷 for (int j = 0; j < oldCap; ++j) { // 桶元素 Node<K,V> e; // 進行桶元素獲取 // 判斷桶元素是否存在(由於使用(n-1)&hash的方式進行計算,因此常常會出現這種狀況) if ((e = oldTab[j]) != null) { // 刪除引用 oldTab[j] = null; // 判斷桶元素是否有下一個元素 if (e.next == null) // 沒有下一個元需.使用相同的算法計算在新桶中的下標並賦值 newTab[e.hash & (newCap - 1)] = e; // 桶元素存在next,判斷是否爲TreeNode else if (e instanceof TreeNode) // 進行委派執行 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order // 對於鏈表結構,拆分到高位與低位兩組 // loHead與loTail非別表明低位頭與低位尾 Node<K,V> loHead = null, loTail = null; // hiHead與hiTail非別表明高位頭與高位尾 Node<K,V> hiHead = null, hiTail = null; // next Node<K,V> next; // 已經存在遍歷目標,直接使用do while do { // 拿到e的next. next = e.next; // 判斷e的hash是不是高位 // 判斷原理以下. // 首先oldCap恆定爲2的n次冪,二進制表達爲1000... // 下標計算方程爲(n-1)&hash // 帶入n後,爲...111&hash // 當n=111時,hash爲1101,結果爲101 // 當n=1111時,hash爲1101,結果爲1101.表示爲高位(注意hash的高位) // 當n=1111時,hash爲0101,結果爲101.表示爲低位(注意hash的高位) // 這樣就,能夠直接求出新的下標.可是,這種方式須要對全部的元素進行從新計算,很是低效 // 因此jdk使用了一個特別的方法.就是直接比較最高位,當一個hash與數組長度(也就是n的n次冪)時,如1101&1000 // 當結果等於0時,表明這個hash是低位hash,其餘就是高位hash if ((e.hash & oldCap) == 0) { // 低位 // 判斷低位尾部是否存在 if (loTail == null) // 不存在,表明頭部也沒有,進行初始化 loHead = e; else // 存在,追加到尾部的next loTail.next = e; // 更新尾部 loTail = e; } else { // 高位 if (hiTail == null) // 不存在,表明頭部也沒有,進行初始化 hiHead = e; else // 存在,追加到尾部的next hiTail.next = e; // 更新尾部 hiTail = e; } } while ((e = next) != null); // 進行收尾處理 // 判斷低位是否爲空 if (loTail != null) { // 不爲空 // 清除末尾元素的next.當loTail是鏈表倒數第二個元素且倒數第一個元素是高位元素時,須要清空loTail的next對高位元素的引用 loTail.next = null; // 低位使用原下標進行保存 newTab[j] = loHead; } if (hiTail != null) { // 不爲空 // 清除末尾元素的next.理由同上但判斷方式相反 hiTail.next = null; // 低位使用原下標+原容量進行保存 newTab[j + oldCap] = hiHead; } } } } } // 返回newTab return newTab; }
treeifyBin
方法當某槽內的鏈表長度大於閾值後,此方法會被調用.將槽內對應位置的鏈表替換爲紅黑樹.
注意:這個方法內只是將鏈表替換成了紅黑樹對象TreeNode
.此時仍是鏈狀結構,沒有組裝成紅黑樹的結構.須要在最後帶用鏈表頭部對象的TreeNode.treeify
方法完成樹化緩存
final void treeifyBin(Node<K,V>[] tab, int hash) { // n - 表明參數tab長度 // index - tab中表示hash的下標 // hash - 待處理的鏈表節點hash // e - 目標節點 int n, index; Node<K,V> e; // 判斷tab是否爲空或tab長度MIN_TREEIFY_CAPACITY=64 // 也就是說,在桶中單個鏈表長度可能已經達到要求(如putVal中的binCount >= TREEIFY_THRESHOLD - 1),可是桶容量未達標時,也不會進行tree化 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) // 表是空的或表容量小於MIN_TREEIFY_CAPACITY // 重置大小 resize(); // 能夠tree化,檢查鏈表節點是否存在 else if ((e = tab[index = (n - 1) & hash]) != null) { // 鏈表節點存在 // 樹節點頭與尾 TreeNode<K,V> hd = null, tl = null; // 已經有第一個目標,直接do while do { // 構造一個TreeNode.(這裏沒有額外邏輯,僅僅是使用當前的e建立了TreeNode) // 注意,這裏的Tree繼承自LinkedHashMap.Entry,內部包含了before與after的雙向鏈表.可是TreeNode又自行實現了雙向鏈表prev與next,並無使用前者的數據結構 TreeNode<K,V> p = replacementTreeNode(e, null); // 判斷尾部是否爲空 if (tl == null) // 初始化頭部 hd = p; else { // 尾部不爲空 // 設置上一個節點 // 設置尾部下一個節點 p.prev = tl; tl.next = p; } // 交換尾部 tl = p; } while ((e = e.next) != null); // 賦值並判斷頭部節點是否爲空 if ((tab[index] = hd) != null) // 調用TreeNode的treeify組裝紅黑樹 hd.treeify(tab); } }
即便被調用,這個方法也不保證替換對應槽內的鏈表到紅黑樹.這還須要檢查當前桶的容量是否達到閾值
MIN_TREEIFY_CAPACITY
數據結構
TreeNode.treeify
方法將鏈表結構的數據轉換成紅黑樹結構的數據的實際執行者(此時鏈表中的全部對象已是TreeNode
類型的了)app
final void treeify(Node<K,V>[] tab) { // 根節點(黑色節點) TreeNode<K,V> root = null; // 進行迭代.(當前this做用域位於TreeNode實例) // x表示當前遍歷中的節點 for (TreeNode<K,V> x = this, next; x != null; x = next) { // 緩存next next = (TreeNode<K,V>)x.next; // 保證當前節點左右節點爲null x.left = x.right = null; // 判斷是否存在根節點 if (root == null) { // 不存在 // 跟節點沒有父級.因此設置爲null x.parent = null; // 紅黑樹中,根節點是黑色的 x.red = false; // 保存到局部變量 root = x; } else { // 跟節點已確認 // 緩存key K k = x.key; // 緩存hash int h = x.hash; // key類型 Class<?> kc = null; // -------------------- 對跟節點進行遍歷,查找插入位置 -------------------- // p是插入節點的父節點 for (TreeNode<K,V> p = root;;) { // dir - 用來表達左右. // ph - 父節點hash int dir, ph; // 父節點key K pk = p.key; // -------------------- 判斷插入到左仍是右節點 -------------------- // 初始化父節點hash // 判斷父節點hash是否大於當前節點hash if ((ph = p.hash) > h) // dir = -1 插入節點在父節點左側 dir = -1; // 判斷父節點hash是否小於當前節點hash else if (ph < h) // dir = 1 插入節點在父節點右側 dir = 1; // 父節點hash等於當前節點hash,進行額外的處理 // 這裏使用了基於Class的一些處理辦法,最終保證了dir的正確值(不爲0) TODO 待補充 else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); // -------------------- 獲取左或右節點並進行操做 -------------------- // 緩存插入節點的父節點 TreeNode<K,V> xp = p; // 使用dir獲取父節點對應的左或右節點,而且檢查這個節點是否爲null.不爲null時,進入下一次循環 if ((p = (dir <= 0) ? p.left : p.right) == null) { // 父節點左或右節點爲null // 設置父級節點 x.parent = xp; // 再次判斷左右 if (dir <= 0) // 將父節點的左子節點複製爲當前節點 xp.left = x; else // 將父節點的右子節點複製爲當前節點 xp.right = x; // 進行平衡 root = balanceInsertion(root, x); // 退出查找插入位置的循環,進行下一個元素的插入 break; } } } } // 由於在進行旋轉操做時,可能會修改根節點到其餘節點.致使桶中的直接節點爲分支節點,因此須要進行修正 moveRootToFront(tab, root); }
hash
方法HashMap
本身使用的hash的計算方式.做爲key比較,index的計算依據.它經過對象原hash與其高16位進行^
運算,而得出一個新值作回hash.性能
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
這裏之因此沒有直接使用key的hash.是爲了應對當key的hash分佈很是差的時候,會間接致使
hash桶的分佈很是差,從而影響性能.因此使用原hash異或(XOR)原hash的高16位,做爲實際使用的hash
這裏之因此使用16:16,而不是8:8:8:8或其餘值,是由於jdk開發者充分考慮了時間,效率,性能等各方面
的狀況後的折中選擇.
同時也是由於當前jdk大多數的hash已經有了較好的分佈,因此也不須要進行過多的處理
計算過程以下
10000000000000000000000000000000
00000000000000001000000000000000
10000000000000001000000000000000this
tableSizeFor
方法通常用做threshold
的初始化工做.他會返回一個大於輸入值的最小的2的冪.已是2的冪時會再次返回它.code
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
直接上流程(暫時忽略cap - 1部分,最後說)
10000 16 n初始狀態
11000 24 n|=n>>>1 等價於 n = 10000|01000 = 11000
11110 30 n|=n>>>2 等價於 n = 11000|00110 = 11110
11111 31 n|=n>>>4 等價於 n = 11110|00001 = 11111
11111 31 略
11111 31 略
100000 32 n+1
最後,經過+1,將...111變爲100...,即2的n次冪這裏使用了一個頗有意思的方式完成了工做,就是輸入值的最高有效位.
經過不斷的向低位複製最高有效位(1),將全部低位換爲1,最終這個值等於(2^n)-1.同時
也是當前數字的最高位能表達的最大值
那麼,再對這個值+1就可使這個值變成2^n.也就是大於輸入值的最小的2的冪cap - 1的做用:
若是輸入值已是2的冪,那麼這個方法應該直接返回他.直接進行-1,使用原邏輯便可
HashIterator
HashMap本身實現的迭代器.主要用來約束對父類成員的引用.同時實現了remove,nextNode,hasNext等必須方法.爲了方便子類實現,在nextNode方法中直接返回了Node類型對象.用來直接獲取key與value
abstract class HashIterator { /** * 下一個節點 */ Node<K,V> next; // next entry to return /** * 當前節點 */ Node<K,V> current; // current entry /** * 修改計數 */ int expectedModCount; // for fast-fail /** * 當前下標(對於父級成員變量table來講,它指向桶中的一個槽(slot)) */ int index; // current slot /** * 構造方法 */ HashIterator() { // 緩存修改計數 expectedModCount = modCount; // 緩存桶 Node<K,V>[] t = table; // 進行置空(這一步是必須的嗎????) current = next = null; // 索引置0(爲啥?????) index = 0; // 檢查桶是否已經初始化 if (t != null && size > 0) { // advance to first entry // 提早獲取並保存next do {} while (index < t.length && (next = t[index++]) == null); } } public final boolean hasNext() { return next != null; } final Node<K,V> nextNode() { // 指向當前桶 Node<K,V>[] t; // 緩存next.準備替換next.同時e做爲結果返回 Node<K,V> e = next; // fast-fail if (modCount != expectedModCount) throw new ConcurrentModificationException(); // next不該爲空 if (e == null) throw new NoSuchElementException(); // -------------------- 尋找next -------------------- // 設置current爲e,next爲e.next // 判斷next是否爲null // 若是爲空,獲取當前桶 // 判斷桶是否爲空(能走到這裏,說明以前已經在桶中獲取了節點,那桶怎麼回事空的呢?????) if ((next = (current = e).next) == null && (t = table) != null) { // 上面的next獲取失敗,這裏使用切換槽位的方式尋找下一個next do {} while (index < t.length && (next = t[index++]) == null); } return e; } public final void remove() { Node<K,V> p = current; if (p == null) throw new IllegalStateException(); // fast-fail if (modCount != expectedModCount) throw new ConcurrentModificationException(); // 刪除當前迭代其中的current current = null; // 獲取key K key = p.key; // 調用父級刪除方法 // 這裏設置了movable爲false,刪除後不移動節點.這個值只對treeNode生效,去要考證設置成false的做用 // 貌似是在迭代器中被設置了false removeNode(hash(key), key, null, false, false); // 更新計數 expectedModCount = modCount; } }
KeyIterator
HashIterator
的實現.包裝了其中的nextNode
方法,返回Node
的key
final class KeyIterator extends HashIterator implements Iterator<K> { public final K next() { return nextNode().key; } }
ValueIterator
HashIterator
的實現.包裝了其中的nextNode
方法,返回Node
的value
final class ValueIterator extends HashIterator implements Iterator<V> { public final V next() { return nextNode().value; } }
EntryIterator
HashIterator
的實現.包裝了其中的nextNode
方法,直接返回了Node
final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> { public final Map.Entry<K,V> next() { return nextNode(); } }
KeySet
繼承了AbstractSet
,並經過內部類的特性,使實現方法經過直接調用父類HashMap
的引用完成完成
final class KeySet extends AbstractSet<K> { /** * 返回hashMap的成員變量size * @return */ public final int size() { return size; } /** * 由於是同名方法,因此只能使用類名.this.MethodName()的方式調用了 */ public final void clear() { HashMap.this.clear(); } /** * 返回一個內部類key迭代器 * @return */ public final Iterator<K> iterator() { return new KeyIterator(); } /** * 調用父類方法 * @param o * @return */ public final boolean contains(Object o) { return containsKey(o); } /** * 調用父類方法.標記不須要匹配值,刪除後重建 * @param key * @return */ public final boolean remove(Object key) { return removeNode(hash(key), key, null, false, true) != null; } /** * 返回一個內部類keySpl迭代器 * @return */ public final Spliterator<K> spliterator() { return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); } /** * 實現forEach * 這裏有個須要注意的地方 * 對於fail-fast,這個方法會在全部元素迭代完成以後進行,才進行判斷 * @param action */ public final void forEach(Consumer<? super K> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } }