一,首先需要了解以下几个问题:
1.为什么要引入红黑数(特殊的平衡二叉树)数据结构
2.引入红黑树HashMap做了哪些改造
3. 红黑树的特性
4.红黑树的具体实现方式
二,逐一解释以上三个问题
1.1 为什么要引入红黑数(特殊的平衡二叉树)数据结构
由于在JDK1.7之前,HashMap的数据结构为:数组 + 链表。数组相当于日常中永到的数据结构Array. 用来确定key-value对所存储的位置。那么为什么又有链表结构?这个要从HashMap散列值生成来讲起。这个具体细节可参考相关文档即可。如果按照Hash值,通过Hash函数来确认桶位,会存在一个问题,就是hash冲突的问题,也就是不同的key可能会产生不一样的hash值。
static final int hash(Object key) { int h;
// 两个值做异或,最终相同的可能性很大 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
所以引入了链表来存储hash值一样的key-value. 如果按照链表的方式存储,随着节点的增加数据会越来越多,这会导致查询节点的时间复杂度会逐渐增加,平均时间复杂度O(n)。 为了提高查询效率,故在JDK1.8中引入了改进方法红黑树。此数据结构的平均查询效率为O(long n) 。
1.2 引入红黑树HashMap做了哪些改造
当链表节点长度超过8时,将链表转换为二叉树。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> 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<K,V> 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<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { // 先将新节点插入到 p.next p.next = newNode(hash, key, value, null); // 如果长度链表长度超过8,则转换为二叉树 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st /** * hash 需要转化二叉树的hash值 */ // 转化为二叉树 treeifyBin(tab, hash); break; } // 存在hash和key都一样的情况,则说明已经存在。直接跳出循环 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; }
1.3 红黑树的特性
1.3.1 什么时红黑树
红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。 《引自百度百科》
1.3.2 红黑树的特点
红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。
1.3.3 红黑树特点
性质1: 节点是红色或黑色。
性质2:根节点是黑色。
性质3:每个叶节点(NIL节点,空节点)是黑色的。
性质4:每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5: 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
//数据结构
static final class TreeNode<K,V> extends LinkedHashMap.Entry<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) { super(hash, key, val, next); }
1.4 红黑树的具体实现方式(重点)
在JDK1.8 HashMap中,转换为红黑树大致分为三个步骤。
第一阶段:将链表转化为二叉树
第二阶段:验证是否满足红黑树的五大特征
第三阶段:对二叉树进行左右旋转操作
1.4.1 将链表转化为二叉树
final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); // 重新计算 hash段位,及table的索引位,第一个节点 else if ((e = tab[index = (n - 1) & hash]) != null) { /************ 双向链表 start***************/ // hd头节点, tl尾节点 TreeNode<K,V> hd = null, tl = null; do { // 循环所有节点 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);// 循环下一个节点 /************ 双向链表 end***************/ // 前面仅仅转换为双向链表,treeify才是转换红黑树的处理方法入口 // 第一个节点赋值为头节点,也就是根节点 if ((tab[index] = hd) != null) // 将二叉树转换为红黑树 hd.treeify(tab); } }
1.4.2 验证是否满足红黑树的五大特征
/** * 调用这个方法之前 也就是一个双向链表 * 初始进入值为 this头节点 * 将双向链表转换为红黑树 * 目标:查询 root 节点 * @param tab */ final void treeify(Node<K,V>[] tab) { TreeNode<K,V> root = null;//root节点 for (TreeNode<K,V> x = this, next; x != null; x = next) { next = (TreeNode<K,V>)x.next; //next 下一个节点 x.left = x.right = null;//设置左右节点为空 if (root == null) {//首次循环 root == null x.parent = null; // 将根节点的父节点设置位空 x.red = false; // 将根节点设置为 black root = x; //将x 设置为根节点 } else {// 非根节点 K k = x.key;// 获取当前循环节点key int h = x.hash;// 获取当前节点hash Class<?> kc = null; // 从根节点开始验证 for (TreeNode<K,V> p = root;;) { int dir, ph; K pk = p.key;// 每个节点的key if ((ph = p.hash) > h) //每个节点的hash 与 外层循环的x.hash做比较 dir = -1;// <0 ,沿左路径查找 -1 else if (ph < h)// >0, 沿右路径查找 1 dir = 1; // 如果存在比较对象,则根据比较对象定义的comparable进行比较 // 比较之后返回查询节点路径(左或右) else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); // p设置位x的父节点 xp TreeNode<K,V> xp = p; // 如果父节点的左节点或右节点为空时,才进行插入操作 if ((p = (dir <= 0) ? p.left : p.right) == null) { // 将px设置为x的父节点 x.parent = xp; if (dir <= 0) xp.left = x; else xp.right = x; // 将二叉树转换位红黑树-正式转换红黑树 root = balanceInsertion(root, x); break; } } } } moveRootToFront(tab, root); }
1.4.3 对二叉树进行左右旋转操作
/** * 转换二叉树为红黑树 * @param root 根节点 * @param x 执行的节点 * @param <K> * @param <V> * @return */ static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) { // 默认x节点为红色节点 x.red = true; /** * xp: x的父节点 * xpp: x父节点的父节点 * xppl: x父节点的父节点左子节点 * xppr: x父节点的父节点右子节点 */ for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { // xp = x.parent // 如果x存在父节点,则说明目前只有一个节点,即root.根据 // 红黑树的五大特征,根节点只能为黑色节点 if ((xp = x.parent) == null) { x.red = false; return x; } //xpp = xp.parent //直接查询的是根节点 else if (!xp.red || (xpp = xp.parent) == null) return root; // xppl = xpp.left // x的父节点时左节点时 if (xp == (xppl = xpp.left)) { // 验证是否需要旋转 // xppr = xpp.right 存在右节点 且 右节点为红色 if ((xppr = xpp.right) != null && xppr.red) { xppr.red = false; // xppr 设置位black xp.red = false; // xp 设置位black xpp.red = true; // xpp 设置位red x = xpp;// 将x赋值为父节点的父节点 } 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); } } } } // x的父节点右节点时 else { // 验证是否需要旋转 if (xppl != null && xppl.red) { xppl.red = false; xp.red = false; xpp.red = true; x = xpp; } else { if (x == xp.left) { // 右旋转 root = rotateRight(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; // 左旋转 root = rotateLeft(root, xpp); } } } } } }
1.4.3.1 左旋转
/** * 左旋转 * @param root * @param p * @param <K> * @param <V> * @return */ static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p) { TreeNode<K,V> r, pp, rl; if (p != null && (r = p.right) != null) { if ((rl = p.right = r.left) != null) rl.parent = p; if ((pp = r.parent = p.parent) == null) (root = r).red = false; else if (pp.left == p) pp.left = r; else pp.right = r; r.left = p; p.parent = r; } return root; }
1.4.3.2 右旋转
/** * 右旋转 * @param root * @param p * @param <K> * @param <V> * @return */ static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p) { // l: p的左节点 pp:p的父节点 lr:左右节点 TreeNode<K,V> l, pp, lr; // 传入参数 // root: 默认调用此方法前指定的root节点 // p: root的父节点 if (p != null && (l = p.left) != null) { if ((lr = p.left = l.right) != null) lr.parent = p; // 判断p的父节点是否为空 if ((pp = l.parent = p.parent) == null) // 调整root的值 (root = l).red = false; else if (pp.right == p) pp.right = l; else pp.left = l; // 将p调整为 root 节点的右节点 l.right = p; //将l调整为p的parent p.parent = l; } return root; }