1.ConcurrentMap的內部結構html
2.ConcurrentMap構造函數node
3.元素新增策略
4.元素刪除
5.元素修改和查找
6.特殊操做
7.擴容
8.總結segmentfault
繼承自abstractMap,實現concurrentMap接口,父類和hashmap類似數組
在開始以前你們應該都瞭解過concurrentHashmap是經過分段鎖的方式實現多線程安全的安全
(這裏瞭解到1.8以後彷佛也拋棄了分段鎖的概念,咱們看看吧)數據結構
concurrentHashMap內部存在一個node數據結構用來存放元素多線程
static class Node<K, V> implements Map.Entry<K, V> { final int hash; final K key; volatile V val; volatile Node<K, V> next; Node(int hash, K key, V val, Node<K, V> next) { this.hash = hash; this.key = key; this.val = val; this.next = next; } @Override public final K getKey() { return key; } @Override public final V getValue() { return val; } //作疑惑操做獲取結果 @Override public final int hashCode() { return key.hashCode() ^ val.hashCode(); } @Override public final String toString() { return key + "=" + val; } @Override public final V setValue(V value) { throw new UnsupportedOperationException(); } @Override public final boolean equals(Object o) { Object k, v, u; Map.Entry<?, ?> e; //是Map.Entry的子類,而且key相同,value相同,且都不爲空 return ((o instanceof Map.Entry) && (k = (e = (Map.Entry<?, ?>) o).getKey()) != null && (v = e.getValue()) != null && (k == key || k.equals(key)) && (v == (u = val) || v.equals(u))); } /** * Virtualized support for map.get(); overridden in subclasses. * 這個是用來尋找某個節點,當作鏈表的方式尋找 */ Node<K, V> find(int h, Object k) { Node<K, V> e = this; if (k != null) { do { K ek; boolean ok = e.hash == h && ((ek = e.key) == k || (ek != null && k.equals(ek))); if (ok) { return e; } } while ((e = e.next) != null); } return null; } }
用來保存樹節點的數據結構併發
static final class TreeNode<K,V> extends Node<K,V> { TreeNode<K,V> parent; // red-black tree links TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; TreeNode(int hash, K key, V val, Node<K,V> next, TreeNode<K,V> parent) { super(hash, key, val, next); this.parent = parent; } @Override Node<K,V> find(int h, Object k) { return findTreeNode(h, k, null); } /** * Returns the TreeNode (or null if not found) for the given key * starting at given root. * 尋找樹節點,二叉查找樹 */ final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) { if (k != null) { TreeNode<K,V> p = this; do { int ph, dir; K pk; TreeNode<K,V> q; TreeNode<K,V> pl = p.left, pr = p.right; if ((ph = p.hash) > h) { //若是當前的hash散列值大於h,那麼目標就在這個節點左邊 p = pl; } else if (ph < h) { //小於就在右邊 p = pr; } else if ((pk = p.key) == k || (pk != null && k.equals(pk))) { //剛好相等直接返回 return p; } else if (pl == null) { //若是爲空,那麼就走另一邊 p = pr; } else if (pr == null) { p = pl; } else if ((kc != null || (kc = comparableClassFor(k)) != null) && (dir = compareComparables(kc, k, pk)) != 0) { p = (dir < 0) ? pl : pr; } else if ((q = pr.findTreeNode(h, k, kc)) != null) { return q; } else { p = pl; } } while (p != null); } return null; } }
若是是默認的構造函數就是啥都沒有就是個空的ide
public TestConcurrentMap() { } private static final int tableSizeFor(int c) { int n = c - 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; } public TestConcurrentMap(int initialCapacity) { if (initialCapacity < 0) { throw new IllegalArgumentException(); } int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); this.sizeCtl = cap; }
這裏的操做就是子啊初始化容量的時候把容量大小控制爲比設置的值要大的最小二次冪!!!函數
爲何呢?由於>>>是無符號右移動,
1.也就是在右移的時候會吧最高位的1移動到下一位,也就是會造成 0。。。010。。。 的數據和0。。。100。。。那麼一個|操做以後那就是0。。。110。。。
2.而後又移動2位,那就是吧以前合併的2個1再日後合併成1
。。。最終的結果就是0000111111111。。。
由於int是4個字節,也就是32位,那麼最後一次只須要又移16位,就能夠吧剩下的一半所有搞定!!!
這裏這樣圖解應該就OK了吧
咱們並無在容器初始化的時候就構建table的實例化,而是在put操做添加數據的時候纔會進行init初始化實例數據
/** * 初始化table大小 * * @return */ private final Node<K, V>[] initTable() { Node<K, V>[] tab; int sc; //開始循環初始化,當表仍是爲空的時候不斷循環 while ((tab = table) == null || tab.length == 0) { if ((sc = sizeCtl) < 0) { // 線程跳轉,若是sc的大小小於0,那麼就切換線程,當小於0的時候表示在別的線程在初始化表或擴展表 Thread.yield(); } //SIZECTL:表示當前對象的內存偏移量,sc表示指望值,-1表示要替換的值,設定爲-1表示要初始化表了 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { //進來以後再判斷一次是否爲空 if ((tab = table) == null || tab.length == 0) { //獲取初始化大小 int n = (sc > 0) ? sc : DEFAULT_CAPACITY; //建立指定長度容量的數組 @SuppressWarnings("unchecked") Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n]; table = tab = nt; //設置sc大小爲去掉1/4,這個就是相似加載因子0.75,實際可用大小 sc = n - (n >>> 2); } } finally { //初始化完畢設置成想要的值 初始化後,sizeCtl長度爲數組長度的3/4 sizeCtl = sc; } break; } } return tab; }
final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) { throw new NullPointerException(); } //二次hash,爲了減小碰撞 int hash = spread(key.hashCode()); int binCount = 0; for (Node<K, V>[] tab = table; ; ) { Node<K, V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) { tab = initTable(); } else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K, V>(hash, key, value, null))) { // no lock when adding to empty bin break; } } else if ((fh = f.hash) == MOVED) { //若是取出來的節點的hash值是MOVED(-1)的話,則表示當前正在對這個數組進行擴容,複製到新的數組,則當前線程也去幫助複製 tab = helpTransfer(tab, f); } else { //若是hash值同樣的位置有值了,那麼就須要對指定的槽位f(指定槽位的引用)上鎖 V oldVal = null; synchronized (f) { //再比較一次 if (tabAt(tab, i) == f) { //fh 這個位置的hash值若是大於0,說明不是在擴容階段 if (fh >= 0) { binCount = 1; for (Node<K, V> e = f; ; ++binCount) { K ek; //判斷hash是否相等,判斷是否在一個槽位,判斷key是否相等,判斷是否一個元素, if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) { //若是hash和key都相等,那麼直接替換值便可 e.val = value; } break; } Node<K, V> pred = e; //指向下一個節點,若是key不等 if ((e = e.next) == null) { //建立新的節點加入 pred.next = new Node<K, V>(hash, key, value, null); break; } } } else if (f instanceof TreeBin) { Node<K, V> p; binCount = 2; if ((p = ((TreeBin<K, V>) f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) { p.val = value; } } } } } //記錄長度 if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD) { treeifyBin(tab, i); } if (oldVal != null) { return oldVal; } break; } } } addCount(1L, binCount); return null; }
對於插入操做,其實和hashmap是同樣的,而後就是多了個擴容不同
其餘至於紅黑樹的平衡,這個能夠參考我以前的博客treeMap應該有詳解-》http://www.javashuo.com/article/p-uohhajvn-ee.html
而且由於在插入的時候有對當前位置上鎖,因此考慮到了多線程的問題,而且在樹進行平衡的時候,也會在加一道鎖
lockRoot(); try { root = balanceInsertion(root, x); } finally { unlockRoot(); }
這樣就保證了在進行平衡操做的時候支持多線程
至於上鎖技術,咱們後面多線程單獨開欄目詳細探討
元素刪除,參考添加,還有以前treemap,這個也很少bb了好吧,實際上是被這個玩意整的有點疲勞。。。
哎,最近寫這個博客耗費太多時間了,也就再也不重複的事情上多作功夫,無非仍是樹的再平衡,和以前treemap差很少的
查找略,參考以前map操做,由於這個不涉及多線程,因此和以前沒差異
//獲取node的類對象 Class<?> ak = Node[].class; //能夠獲取對象第一個元素的偏移地址 ABASE = U.arrayBaseOffset(ak); //能夠獲取數組的轉換因子,也就是數組中元素的增量地址。 //將arrayBaseOffset與arrayIndexScale配合使用,能夠定位數組中每一個元素在內存中的位置。 int scale = U.arrayIndexScale(ak); if ((scale & (scale - 1)) != 0) { throw new Error("data type scale not a power of two"); } // 給定一個int類型數據,返回這個數據的二進制串中從最左邊算起連續的「0」的總數量。由於int類型的數據長度爲32因此高位不足的地方會以「0」填充。 //這個就是計算地址有多少位的長度,也就是一個NODE對象的偏移 ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) { //讀取tab這個對象中的數據,第二個參數是偏移量,好比第i個,那麼就偏移i個對象的大小ashift return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); } //經過cas操做設置值 static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) { //和C比較,設置爲V return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); } //設置指定位置的數據爲V static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) { U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); }
這個地方是經過unsafe計算出NODE這個類起始位置的偏移量大小,而後經過unsafe計算每一個指定的元素的偏移位置,而後把數據值設置進去,unsafe提供硬件級別的操做
再put元素的時候,咱們添加完元素以後,咱們會判斷鏈表長度是否超出8個,若是是轉換爲紅黑樹,而後判斷數組hash桶的長度是否超過64,若是小於64那麼就擴大爲原來的2倍
1.也就是說,擴容發生在鏈表轉換紅黑樹的時候
2.還有一個狀況就是添加完畢元素以後,會有個addCount這個方法也會觸發擴容
在轉換爲紅黑樹的時候,咱們會調用treeifyBin 方法,這個時候判斷若是長度小於64的時候,會調用tryPresize 這個方法進行擴容
/** * 在同一個節點的個數超過8個的時候,會調用treeifyBin方法來看看是擴容仍是轉化爲一棵樹 * @param tab * @param index */ private final void treeifyBin(Node<K,V>[] tab, int index) { Node<K,V> b; int n, sc; if (tab != null) { //先判斷是否這個tab列表的長度小於64 if ((n = tab.length) < MIN_TREEIFY_CAPACITY) { //tryPresize方法把數組長度擴大到原來的兩倍 tryPresize(n << 1); } else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { synchronized (b) { //使用synchronized同步器,將該節點出的鏈表轉爲樹 if (tabAt(tab, index) == b) { TreeNode<K,V> hd = null, tl = null; //hd:樹的頭(head) //循環遍歷鏈表 for (Node<K,V> e = b; e != null; e = e.next) { //依次轉換爲treeNode TreeNode<K,V> p = new TreeNode<K,V>(e.hash, e.key, e.val, null, null); //把Node組成的鏈表,轉化爲TreeNode的鏈表,頭結點任然放在相同的位置 //這裏吧p的前置節點設置爲上一個節點,也就是尾插法 if ((p.prev = tl) == null) { hd = p; } else { tl.next = p; } tl = p; } //吧這個TreeNode組成的鏈表設置進入指定的位置 setTabAt(tab, index, new TreeBin<K,V>(hd)); } } } } }
而後進入tryPreSize判斷是直接開始擴容,仍是多線程輔助幫助一塊兒擴容
/** * 擴容表爲指能夠容納指定個數的大小(老是2的N次方) * 假設原來的數組長度爲16,則在調用tryPresize的時候,size參數的值爲16<<1(32),此時sizeCtl的值爲12 * 計算出來c的值爲64 * 第一次擴容以後 數組長:32 sizeCtl:24 * 第二次擴容以後 數組長:64 sizeCtl:48 * 第二次擴容以後 數組長:128 sizeCtl:94 --> 這個時候纔會退出擴容 */ private final void tryPresize(int size) { //大小變爲2的冪次,size是原來大小的2倍(擴容的時候) /* * MAXIMUM_CAPACITY = 1 << 30 * 若是給定的大小大於等於數組容量的一半,則直接使用最大容量, * 不然使用tableSizeFor算出來 * 後面table一直要擴容到這個值小於等於sizeCtrl(數組長度的3/4)才退出擴容 */ int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(size + (size >>> 1) + 1); int sc; //判斷當前實際容量是否大於0 while ((sc = sizeCtl) >= 0) { Node<K,V>[] tab = table; int n; //sc是這個對象的可用實際容量,判斷tab是否被初始化 if (tab == null || (n = tab.length) == 0) { //基礎hash桶沒有被初始化 //那麼就初始化爲計算出來的c和原來的sc中大的那個 n = (sc > c) ? sc : c; //吧this對象的sizeCtl比較sc判斷是不是sc,若是能夠設置爲-1 if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { //判斷是否當前線程操做 if (table == tab) { @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = nt; //計算設置新的sc,設置爲容量大小的0.75 sc = n - (n >>> 2); } } finally { sizeCtl = sc; } } } else if (c <= sc || n >= MAXIMUM_CAPACITY) { //若是容量c比原來的sc還要小,或者數組長度比最大容量還大那麼就不用擴容了 break; } else if (tab == table) { //獲取高位0的個數 int rs = resizeStamp(n); if (sc < 0) { //若是sc<0 說明還在擴容過程當中 Node<K,V>[] nt; //判斷可否加入一塊兒擴容 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0) { break; } /* * transfer的線程數加一,該線程將進行transfer的幫忙 * 在transfer的時候,sc表示在transfer工做的線程數 */ if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { transfer(tab, nt); } } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) { transfer(tab, null); } } } }
最後咱們看看真正的擴容邏輯
transfer
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { int n = tab.length, stride; //NCPU個數,判斷每一個核心處理的個數是否小於最小處理個數,若是是,那麼就設置成16 //吧數組長度/8而後再除以cpu核心數,若是小於16,那麼就改成16,若是若是不是小於16,那麼就用這個長度做爲這個線程的處理個數 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) { stride = MIN_TRANSFER_STRIDE; // subdivide range } /* * 若是複製的目標nextTab爲null的話,則初始化一個table兩倍長的nextTab * 此時nextTable被設置值了(在初始狀況下是爲null的) * 由於若是有一個線程開始了表的擴張的時候,其餘線程也會進來幫忙擴張, * 而只是第一個開始擴張的線程須要初始化下目標數組 */ if (nextTab == null) { // initiating try { //直接建立新的node數組,長度加大一倍 @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; nextTab = nt; } catch (Throwable ex) { // try to cope with OOME sizeCtl = Integer.MAX_VALUE; return; } //設置到nextTable上 nextTable = nextTab; //設置偏移量 transferIndex = n; } int nextn = nextTab.length; //建立forwardingnode對象,這個是用來控制併發的,當一個節點爲空或已經被轉移以後,就設置爲fwd節點 ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); //是否繼續向前查找的標誌位 boolean advance = true; //在完成以前從新在掃描一遍數組,看看有沒完成的 boolean finishing = false; // to ensure sweep before committing nextTab for (int i = 0, bound = 0;;) { Node<K,V> f; int fh; //判斷是否進入擴容 while (advance) { int nextIndex, nextBound; if (--i >= bound || finishing) { advance = false; } else if ((nextIndex = transferIndex) <= 0) { i = -1; advance = false; } else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) { //設置偏移量爲,nextIndex > stride ? nextIndex - stride : 0 bound = nextBound; i = nextIndex - 1; advance = false; } } if (i < 0 || i >= n || i + n >= nextn) { int sc; //判斷是否完成 if (finishing) { nextTable = null; table = nextTab; sizeCtl = (n << 1) - (n >>> 1); return; } if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) { return; } finishing = advance = true; i = n; // recheck before commit } } else if ((f = tabAt(tab, i)) == null) { //數組中把null的元素設置爲ForwardingNode節點(hash值爲MOVED[-1]) advance = casTabAt(tab, i, null, fwd); } else if ((fh = f.hash) == MOVED) { //已經進入擴容 advance = true; // already processed } else { //f是指向第i個位置的節點 synchronized (f) { if (tabAt(tab, i) == f) { //下面關鍵是把鏈表拆分爲2個部分 Node<K,V> ln, hn; if (fh >= 0) { //該節點的hash值大於等於0,說明是一個Node節點 /* * 由於n的值爲數組的長度,且是power(2,x)的,因此,在&操做的結果只多是0或者n * 根據這個規則,咱們計算hash位置的的時候 * 把高16和低16位進行異或操做 * (h ^ (h >>> 16)) & HASH_BITS * 0--> 放在新表的相同位置 * n--> 放在新表的(n+原來位置) * fh就是指定節點的hash值 * 若是仍是0那麼就是這個hash值和n的取餘操做仍是0,若是是n * n的值是老數組的長度,用來判斷位置是否改變 * 咱們取餘的方式使(n-1)&hashcode 由於n是2的倍數因此是01111111111&hashcode * 由於是擴大了2倍那麼新的數組的取餘方式其實就是n&hashcode * * 由於以前取餘是(n-1)&hashcode 而後若是數組擴大2倍,也就是新的鏈表上要定位的話,其實應該是(2n-1)hashcode 比以前多了一位,若是仍是定位到多的這一位的下面,那麼就不須要進行移動,也就是說有可能形成定位位置不同的話,只有2n所在的最高的那一位的1 n:000100000000 -》 n-1 : 000011111111 2n: 001000000000 -》2n-1: 000111111111 也就是咱們只要計算hashcode&n就能夠知道應該換位置,仍是和原來的位置保持一致了 */ int runBit = fh & n; //指定節點位置 Node<K,V> lastRun = f; /* * lastRun 表示的是須要複製的最後一個節點 * 每當新節點的hash&n -> b 發生變化的時候,就把runBit設置爲這個結果b * 這樣for循環以後,runBit的值就是最後不變的hash&n的值 * 而lastRun的值就是最後一次致使hash&n 發生變化的節點(假設爲p節點) * 爲何要這麼作呢?由於p節點後面的節點的hash&n 值跟p節點是同樣的, * 因此在複製到新的table的時候,它確定仍是跟p節點在同一個位置 * 在複製完p節點以後,p節點的next節點仍是指向它原來的節點,就不須要進行復制了,本身就被帶過去了 * 這也就致使了一個問題就是複製後的鏈表的順序並不必定是原來的倒序 * runBit的值就是最後不變的hash&n的值 是值在這個鏈表中的最後一次變化了的位置 */ for (Node<K,V> p = f.next; p != null; p = p.next) { //p的hash值&n的結果,由於N是擴大2倍,那麼值錢的node節點若是hash值是小於n的結果對新的位置進行取餘,位置仍是原來的位置 int b = p.hash & n; //n的值爲擴張前的數組的長度 //判斷從這位置開始是否會改變位置,若是老的hash值跟n進行&操做仍是保持不變,那麼擴容以後仍是原來的位置 if (b != runBit) { runBit = b; //獲取最後一次變化hash&n的節點位置 lastRun = p; } } //若是runBit==0,說明低位重用 if (runBit == 0) { ln = lastRun; hn = null; } else { // 若是最後更新的 runBit 是 1, 設置高位節點 hn = lastRun; ln = null; } /* * 構造兩個鏈表,順序大部分和原來是反的 * 分別放到原來的位置和新增長的長度的相同位置(i/n+i) */ for (Node<K,V> p = f; p != lastRun; p = p.next) { int ph = p.hash; K pk = p.key; V pv = p.val; if ((ph & n) == 0) { //若是是0那麼建立節點拼接到ln上 ln = new Node<K,V>(ph, pk, pv, ln); } else { //不然拼接到hn上 hn = new Node<K,V>(ph, pk, pv, hn); } } //吧鏈表定位到第i個位置上 setTabAt(nextTab, i, ln); //吧第二個連接到新擴大的節點上n setTabAt(nextTab, i + n, hn); //吧原來的位置的i位置設置爲fwd標識正在擴容 setTabAt(tab, i, fwd); advance = true; } else if (f instanceof TreeBin) { //判斷這個位置的節點是不是一個樹形的節點 TreeBin<K,V> t = (TreeBin<K,V>)f; TreeNode<K,V> lo = null, loTail = null; TreeNode<K,V> hi = null, hiTail = null; int lc = 0, hc = 0; for (Node<K,V> e = t.first; e != null; e = e.next) { int h = e.hash; TreeNode<K,V> p = new TreeNode<K,V> (h, e.key, e.val, null, null); if ((h & n) == 0) { if ((p.prev = loTail) == null) { lo = p; } else { loTail.next = p; } loTail = p; ++lc; } else { if ((p.prev = hiTail) == null) { hi = p; } else { hiTail.next = p; } hiTail = p; ++hc; } } /* * 在複製完樹節點以後,判斷該節點處構成的樹還有幾個節點, * 若是≤6個的話,就轉回爲一個鏈表 */ ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K,V>(lo) : t; hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K,V>(hi) : t; setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } } } } } }
還有一點,由於是多線程安全的,這裏有個特殊操做,當進行擴容的時候,put操做進去會有個helpTransfer的函數,協助擴容,沒錯也就是多線程擴容,這就很NB了
至於擴容的操做咱們後面再講,反正這裏須要知道的是這個方法如何參與進去,造成多線程擴容的做用
在擴容的時候,有個疑問!!!
爲何int runBit=fh&n;的值能夠判斷這個節點位置的鏈表是否須要進行重定位
這個地方我網上找了不少地方都沒有明確說明是爲何。
其實要解答這個問題還得從hashMap的取餘方式提及,這個在我以前的hashmap篇有說明
由於以前取餘是(n-1)&hashcode 而後若是數組擴大2倍,也就是新的鏈表上要定位的話,其實應該是(2n-1)hashcode
比以前多了一位,若是仍是定位到多的這一位的下面,那麼就不須要進行移動,也就是說有可能形成定位位置不同的話,只有2n所在的最高的那一位的1
n:000100000000 -》 n-1 : 000011111111
2n: 001000000000 -》2n-1: 000111111111
也就是咱們只要計算hashcode&n就能夠知道應該換位置,仍是和原來的位置保持一致了
參考:
http://www.javashuo.com/article/p-sddytdhs-kg.html
http://www.javashuo.com/article/p-nbcihcql-bt.html
https://blog.csdn.net/xia744510124/article/details/89478031