摘要:考慮兩大情況,已經(jīng)存在這個紅黑樹中當(dāng)中了,就直接放回對應(yīng)的那個節(jié)點從紅黑樹的節(jié)點開始遍歷,定位到要插入的葉子節(jié)點,插入新節(jié)點除了要維護紅黑樹的平衡外可以參考源碼,還需要維護節(jié)點之間的前后關(guān)系,這里似乎同時是在維護雙向鏈表關(guān)系。
舉例一個入口,利用一個Map構(gòu)造HashMap時
/** * Constructs a new HashMap with the same mappings as the * specified Map. The HashMap is created with * default load factor (0.75) and an initial capacity sufficient to * hold the mappings in the specified Map. * * @param m the map whose mappings are to be placed in this map * @throws NullPointerException if the specified map is null */ public HashMap(Map extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
然后就是調(diào)用putMapEntries方法,第二個參數(shù)其實可以看作細節(jié),個人認為它和HashMap的子類LinkedHashMap有關(guān),evict是逐出的意思,如果基于LinkedHashMap實現(xiàn)LRU緩存的話,這個evict參數(shù)正好就用上了。
/** * Implements Map.putAll and Map constructor * * @param m the map * @param evict false when initially constructing this map, else * true (relayed to method afterNodeInsertion). */ final void putMapEntries(Map extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0) { if (table == null) { // pre-size float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t); } else if (s > threshold) resize(); for (Map.Entry extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } }
可以看到在for循環(huán)中遍歷舊的entrySet視圖,然后將一個個的key-value對放入新構(gòu)造的HashMap中,
for (Map.Entry extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); }
展開putVal(hash(key), key, value, false, evict);
/** * Implements Map.put and related methods * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don"t change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node[] tab; Node p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode )p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } 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; }
通過hash(key)定位到HashMap中tab數(shù)組的索引,如果這個數(shù)組元素的頭節(jié)點正好是TreeNode類型,那么就將執(zhí)行
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
此時this是HashMap自身。
putTreeVal考慮兩大情況,
1)key已經(jīng)存在這個紅黑樹中當(dāng)中了,就直接放回對應(yīng)的那個節(jié)點;
2)從紅黑樹的root節(jié)點開始遍歷,定位到要插入的葉子節(jié)點,插入新節(jié)點;
putTreeVal除了要維護紅黑樹的平衡外(可以參考TreeMap源碼),還需要維護節(jié)點之間的前后關(guān)系,這里似乎同時是在維護雙向鏈表關(guān)系。
/** * Tree version of putVal. */ final TreeNodeputTreeVal(HashMap map, Node [] tab, int h, K k, V v) { Class> kc = null; boolean searched = false; TreeNode root = (parent != null) ? root() : this; for (TreeNode p = root;;) { int dir, ph; K pk; if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((pk = p.key) == k || (k != null && k.equals(pk))) return p; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) { if (!searched) { TreeNode q, ch; searched = true; if (((ch = p.left) != null && (q = ch.find(h, k, kc)) != null) || ((ch = p.right) != null && (q = ch.find(h, k, kc)) != null)) return q; } dir = tieBreakOrder(k, pk); } TreeNode xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { Node xpn = xp.next; TreeNode x = map.newTreeNode(h, k, v, xpn); if (dir <= 0) xp.left = x; else xp.right = x; xp.next = x; x.parent = x.prev = xp; if (xpn != null) ((TreeNode )xpn).prev = x; moveRootToFront(tab, balanceInsertion(root, x)); return null; } } }
下面重點分析putTreeVal方法
1 首先找到root節(jié)點,
TreeNoderoot = (parent != null) ? root() : this;
這里的this是指TreeNode自己,從某個節(jié)點一直往上溯,直到parent==null的情況
2 遞歸遍歷root
判斷節(jié)點之間的hash大小,如果hash值相等采用key比較等
然后采用左子樹或者右子樹,繼續(xù)遍歷
(關(guān)于key值大小的比較算是細節(jié)的地方,這里暫且代入String類型的key解讀源碼以圖整體思路流暢)
3 如果遍歷到了葉子節(jié)點
比如上一步采用左子樹,而左子樹剛好是葉子節(jié)點,p == null
此時遞歸遍歷結(jié)束
if ((p = (dir <= 0) ? p.left : p.right) == null) { Nodexpn = xp.next; TreeNode x = map.newTreeNode(h, k, v, xpn); if (dir <= 0) xp.left = x; else xp.right = x; xp.next = x; x.parent = x.prev = xp; if (xpn != null) ((TreeNode )xpn).prev = x; moveRootToFront(tab, balanceInsertion(root, x)); return null; }
xp是葉子節(jié)點的父節(jié)點,這個節(jié)點不是null,葉子節(jié)點p一定是null
新增一個節(jié)點x,next指向原來父節(jié)點的.next,x就是新增的葉子節(jié)點
1) 處理紅黑樹的關(guān)系
父節(jié)點xp和葉子節(jié)點x的關(guān)系,落在左子樹還是右子樹;
x的parent指向父節(jié)點xp x.parent = xp
最后保持紅黑樹平衡
2)處理雙向鏈表的關(guān)系
類似于在xp-->xpn(xp.next)中間插入新的節(jié)點x,
即
x = map.newTreeNode(h, k, v, xpn); x.prev = xp //如果xpn不是null,則處理xpn的prev ((TreeNode)xpn).prev = x;
圖示
3) 保持紅黑樹平衡
moveRootToFront(tab, balanceInsertion(root, x));
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/71407.html
摘要:存儲結(jié)構(gòu)在中,的實現(xiàn)采用了數(shù)組鏈表紅黑樹的復(fù)雜結(jié)構(gòu),數(shù)組的一個元素又稱作桶。當(dāng)一個鏈表的元素個數(shù)達到一定的數(shù)量且數(shù)組的長度達到一定的長度后,則把鏈表轉(zhuǎn)化為紅黑樹,從而提高效率。 簡介 HashMap采用key/value存儲結(jié)構(gòu),每個key對應(yīng)唯一的value,查詢和修改的速度都很快,能達到O(1)的平均時間復(fù)雜度。它是非線程安全的,且不保證元素存儲的順序; 繼承體系 showImg(...
摘要:表示該類本身不可比表示與對應(yīng)的之間不可比。當(dāng)數(shù)目滿足時,鏈表將轉(zhuǎn)為紅黑樹結(jié)構(gòu),否則繼續(xù)擴容。至此,插入告一段落。當(dāng)超出時,哈希表將會即內(nèi)部數(shù)據(jù)結(jié)構(gòu)重建至大約兩倍。要注意的是使用許多有這相同的鍵值肯定會降低哈希表性能。 回顧上期?觀光線路圖:putAll() --> putMapEntries() --> tableSizeFor() --> resize() --> hash() --...
前面已經(jīng)說明了HashMap以及紅黑樹的一些基本知識,對JDK8的HashMap也有了一定的了解,本篇就開始看看并發(fā)包下的ConcurrentHashMap,說實話,還是比較復(fù)雜的,筆者在這里也不會過多深入,源碼層次上了解一些主要流程即可,清楚多線程環(huán)境下整個Map的運作過程就算是很大進步了,更細的底層部分需要時間和精力來研究,暫不深入 前言 jdk版本:1.8 JDK7中,ConcurrentH...
摘要:當(dāng)一個值中要存儲到的時候會根據(jù)的值來計算出他的,通過哈希來確認到數(shù)組的位置,如果發(fā)生哈希碰撞就以鏈表的形式存儲在源碼分析中解釋過,但是這樣如果鏈表過長來的話,會把這個鏈表轉(zhuǎn)換成紅黑樹來存儲。 正文開始 注:JDK版本為1.8 HashMap1.8和1.8之前的源碼差別很大 目錄 簡介 數(shù)據(jù)結(jié)構(gòu) 類結(jié)構(gòu) 屬性 構(gòu)造方法 增加 刪除 修改 總結(jié) 1.HashMap簡介 H...
閱讀 3387·2021-11-22 15:22
閱讀 2371·2021-09-06 15:00
閱讀 871·2020-06-22 14:39
閱讀 3704·2019-08-30 15:56
閱讀 1540·2019-08-30 12:55
閱讀 3260·2019-08-29 17:19
閱讀 3231·2019-08-26 11:41
閱讀 613·2019-08-23 17:14