商务网站开发课程建言,有没有专门做建筑造价的私单网站,黑河网站建设,搜索引擎实训心得体会转载自 ConcurrentHashMap的红黑树实现分析红黑树
红黑树是一种特殊的二叉树#xff0c;主要用它存储有序的数据#xff0c;提供高效的数据检索#xff0c;时间复杂度为O(lgn)#xff0c;每个节点都有一个标识位表示颜色#xff0c;红色或黑色#xff0c;有如下5种特性主要用它存储有序的数据提供高效的数据检索时间复杂度为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(NodeK,V[] tab, intindex) { NodeK,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) { TreeNodeK,V hd null, tl null; for(NodeK,V e b; e ! null; e e.next) { TreeNodeK,V p newTreeNodeK,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, newTreeBinK,V(hd)); } } } }}从上述实现可以看出并非一开始就创建红黑树结构如果当前Node数组长度小于阈值MIN_TREEIFY_CAPACITY默认为64先通过扩大数组容量为原来的两倍以缓解单个链表元素过大的性能问题。
红黑树构造过程
下面对红黑树的构造过程进行分析1、通过遍历Node链表生成对应的TreeNode链表其中TreeNode在实现上继承了Node类
12345678classTreeNodeK,V extendsNodeK,V { TreeNodeK,V parent; // red-black tree links TreeNodeK,V left; TreeNodeK,V right; TreeNodeK,V prev; // needed to unlink next upon deletion booleanred;}假设TreeNode链表如下其中节点中的数值代表hash值2、根据TreeNode链表初始化TreeBin类对象TreeBin在实现上同样继承了Node类所以初始化完成的TreeBin类对象可以保持在Node数组中
12345678910111213classTreeBinK,V extendsNodeK,V { TreeNodeK,V root; volatileTreeNodeK,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中明确规定根节点必须是黑色的
123456789for(TreeNodeK,V x b, next; x ! null; x next) { next (TreeNodeK,V)x.next; x.left x.right null; if(r null) { x.parent null; x.red false; r x; } ...二叉树结构4、加入节点60如果root不为空则通过比较节点hash值的大小将新节点插入到指定位置实现如下
12345678910111213141516171819202122232425K k x.key;inth x.hash;Class? kc null;for(TreeNodeK,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); TreeNodeK,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方法实现如下
123456789staticint 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其父节点为空直接返回。
123456if((xp x.parent) null) { x.red false; returnx;}elseif (!xp.red || (xpp xp.parent) null) returnroot;调整之后的二叉树②、加入节点50二叉树如下继续执行balanceInsertion()方法调整二叉树此时节点50的父节点60是左儿子走如下逻辑
123456789101112131415161718192021if(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执行右旋操作右旋实现如下
1234567891011121314151617staticK,V TreeNodeK,V rotateRight(TreeNodeK,V root, TreeNodeK,V p) { TreeNodeK,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不为空且是红色的则执行如下逻辑
123456if(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二叉树变化如下重新对节点进行着色到此为止红黑树已经构造完成