ConcurrentHashMap的红黑树实现分析
轉載自?ConcurrentHashMap的紅黑樹實現分析
紅黑樹
紅黑樹是一種特殊的二叉樹,主要用它存儲有序的數據,提供高效的數據檢索,時間復雜度為O(lgn),每個節點都有一個標識位表示顏色,紅色或黑色,有如下5種特性:
1、每個節點要么紅色,要么是黑色;
2、根節點一定是黑色的;
3、每個空葉子節點必須是黑色的;
4、如果一個節點是紅色的,那么它的子節點必須是黑色的;
5、從一個節點到該節點的子孫節點的所有路徑包含相同個數的黑色節點;
結構示意圖
只要滿足以上5個特性的二叉樹都是紅黑樹,當有新的節點加入時,有可能會破壞其中一些特性,需要通過左旋或右旋操作調整樹結構,重新著色,使之重新滿足所有特性。
ConcurrentHashMap紅黑樹實現
《談談ConcurrentHashMap1.7和1.8的不同實現》一文中已經提到,在1.8的實現中,當一個鏈表中的元素達到8個時,會調用treeifyBin()方法把鏈表結構轉化成紅黑樹結構,實現如下:
| 1234567891011121314151617181920212223242526272829 | /**?* Replaces all linked nodes in bin at given index unless table is?* too small, in which case resizes instead.?*/privatefinal void treeifyBin(Node<K,V>[] tab, intindex) {????Node<K,V> b; intn, sc;????if(tab != null) {????????if((n = tab.length) < MIN_TREEIFY_CAPACITY)????????????tryPresize(n << 1);????????elseif ((b = tabAt(tab, index)) != null&& b.hash >= 0) {????????????synchronized(b) {????????????????if(tabAt(tab, index) == b) {????????????????????TreeNode<K,V> hd = null, tl = null;????????????????????for(Node<K,V> e = b; e != null; e = e.next) {????????????????????????TreeNode<K,V> p =????????????????????????????newTreeNode<K,V>(e.hash, e.key, e.val,??????????????????????????????????????????????null,null);????????????????????????if((p.prev = tl) == null)????????????????????????????hd = p;????????????????????????else????????????????????????????tl.next = p;????????????????????????tl = p;????????????????????}????????????????????setTabAt(tab, index, newTreeBin<K,V>(hd));????????????????}????????????}????????}????}} |
從上述實現可以看出:并非一開始就創建紅黑樹結構,如果當前Node數組長度小于閾值MIN_TREEIFY_CAPACITY,默認為64,先通過擴大數組容量為原來的兩倍以緩解單個鏈表元素過大的性能問題。
紅黑樹構造過程
下面對紅黑樹的構造過程進行分析:
1、通過遍歷Node鏈表,生成對應的TreeNode鏈表,其中TreeNode在實現上繼承了Node類;
| 12345678 | classTreeNode<K,V> extendsNode<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????booleanred;} |
假設TreeNode鏈表如下,其中節點中的數值代表hash值:
2、根據TreeNode鏈表初始化TreeBin類對象,TreeBin在實現上同樣繼承了Node類,所以初始化完成的TreeBin類對象可以保持在Node數組中;
| 12345678910111213 | classTreeBin<K,V> extendsNode<K,V> {????TreeNode<K,V> root;????volatileTreeNode<K,V> first;????volatileThread waiter;????volatileint lockState;????// values for lockState????// set while holding write lock????staticfinal int WRITER = 1;????// set when waiting for write lock????staticfinal int WAITER = 2;????// increment value for setting read lock????staticfinal int READER = 4;} |
3、遍歷TreeNode鏈表生成紅黑樹,一開始二叉樹的根節點root為空,則設置鏈表中的第一個節點80為root,并設置其red屬性為false,因為在紅黑樹的特性1中,明確規定根節點必須是黑色的;
| 123456789 | for(TreeNode<K,V> x = b, next; x != null; x = next) {????next = (TreeNode<K,V>)x.next;????x.left = x.right = null;????if(r == null) {????????x.parent = null;????????x.red = false;????????r = x;????}????... |
二叉樹結構:
4、加入節點60,如果root不為空,則通過比較節點hash值的大小將新節點插入到指定位置,實現如下:
| 12345678910111213141516171819202122232425 | K k = x.key;inth = x.hash;Class<?> kc = null;for(TreeNode<K,V> p = r;;) {????intdir, ph;????K pk = p.key;????if((ph = p.hash) > h)????????dir = -1;????elseif (ph < h)????????dir = 1;????elseif ((kc == null&&??????????????(kc = comparableClassFor(k)) == null) ||?????????????(dir = compareComparables(kc, k, pk)) == 0)????????dir = tieBreakOrder(k, pk);????????TreeNode<K,V> xp = p;????if((p = (dir <= 0) ? p.left : p.right) == null) {????????x.parent = xp;????????if(dir <= 0)????????????xp.left = x;????????else????????????xp.right = x;????????r = balanceInsertion(r, x);????????break;????}} |
其中x代表即將插入到紅黑樹的節點,p指向紅黑樹中當前遍歷到的節點,從根節點開始遞歸遍歷,x的插入過程如下:
1)、如果x的hash值小于p的hash值,則判斷p的左節點是否為空,如果不為空,則把p指向其左節點,并繼續和p進行比較,如果p的左節點為空,則把x指向的節點插入到該位置;
2)、如果x的hash值大于p的hash值,則判斷p的右節點是否為空,如果不為空,則把p指向其右節點,并繼續和p進行比較,如果p的右節點為空,則把x指向的節點插入到該位置;
3)、如果x的hash值和p的hash值相等,怎么辦?
解決:首先判斷節點中的key對象的類是否實現了Comparable接口,如果實現Comparable接口,則調用compareTo方法比較兩者key的大小,但是如果key對象沒有實現Comparable接口,或則compareTo方法返回了0,則繼續調用tieBreakOrder方法計算dir值,tieBreakOrder方法實現如下:
| 123456789 | staticint tieBreakOrder(Object a, Object b) {????intd;????if(a == null|| b == null||????????(d = a.getClass().getName().?????????compareTo(b.getClass().getName())) == 0)????????d = (System.identityHashCode(a) <= System.identityHashCode(b) ??????????????-1: 1);????returnd;} |
最終比較key對象的默認hashCode()方法的返回值,因為System.identityHashCode(a)調用的是對象a默認的hashCode();
插入節點60之后的二叉樹:
5、當有新節點加入時,可能會破壞紅黑樹的特性,需要執行balanceInsertion()方法調整二叉樹,使之重新滿足特性,方法中的變量xp指向x的父節點,xpp指向xp父節點,xppl和xppr分別指向xpp的左右子節點,balanceInsertion()方法首先會把新加入的節點設置成紅色。
①、加入節點60之后,此時xp指向節點80,其父節點為空,直接返回。
| 123456 | if((xp = x.parent) == null) {????x.red = false;????returnx;}elseif (!xp.red || (xpp = xp.parent) == null)????returnroot; |
調整之后的二叉樹:
②、加入節點50,二叉樹如下:
繼續執行balanceInsertion()方法調整二叉樹,此時節點50的父節點60是左兒子,走如下邏輯:
| 123456789101112131415161718192021 | if(xp == (xppl = xpp.left)) {????if((xppr = xpp.right) != null&& xppr.red) {????????xppr.red = false;????????xp.red = false;????????xpp.red = true;????????x = xpp;????}????else{????????if(x == xp.right) {????????????root = rotateLeft(root, x = xp);????????????xpp = (xp = x.parent) == null? null: xp.parent;????????}????????if(xp != null) {????????????xp.red = false;????????????if(xpp != null) {????????????????xpp.red = true;????????????????root = rotateRight(root, xpp);????????????}????????}????}} |
根據上述邏輯,把節點60設置成黑色,把節點80設置成紅色,并對節點80執行右旋操作,右旋實現如下:
| 1234567891011121314151617 | static<K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,???????????????????????????????????????TreeNode<K,V> p) {????TreeNode<K,V> l, pp, lr;????if(p != null&& (l = p.left) != null) {????????if((lr = p.left = l.right) != null)????????????lr.parent = p;????????if((pp = l.parent = p.parent) == null)????????????(root = l).red = false;????????elseif (pp.right == p)????????????pp.right = l;????????else????????????pp.left = l;????????l.right = p;????????p.parent = l;????}????returnroot;} |
右旋之后的紅黑樹如下:
③、加入節點70,二叉樹如下:
繼續執行balanceInsertion()方法調整二叉樹,此時父節點80是個右兒子,節點70是左兒子,且叔節點50不為空,且是紅色的,則執行如下邏輯:
| 123456 | if(xppl != null&& xppl.red) {????xppl.red = false;????xp.red = false;????xpp.red = true;????x = xpp;} |
此時二叉樹如下:
此時x指向xpp,即節點60,繼續循環處理x,設置其顏色為黑色,最終二叉樹如下:
④、加入節點20,二叉樹變化如下:
因為節點20的父節點50是一個黑色的節點,不需要進行調整;
⑤、加入節點65,二叉樹變化如下:
對節點80進行右旋操作。
⑥、加入節點40,二叉樹變化如下:
1、對節點20執行左旋操作;
2、對節點50執行右旋操作;
最后加入節點10,二叉樹變化如下:
重新對節點進行著色,到此為止,紅黑樹已經構造完成;
總結
以上是生活随笔為你收集整理的ConcurrentHashMap的红黑树实现分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 列席人员指的是什么 列席人员简单介绍
- 下一篇: 集合总结(Collection)