集合底层源码分析之HashMap《下》(三)

源码分析

紧接上一章节,本章节主要讲解HashMap的删除和查找完整操作流程(建议本内容从头到尾看,先看源码再看流程图,否则你会看的很懵逼。)。

getTreeNode()方法源码分析

    /**
     * 调用根节点的find
     */
    final TreeNode<K,V> getTreeNode(int h, Object k) {
        //判断父节点不等于null,先找到root节点否则从当前节点开始寻找相同节点     
        return ((parent != null) ? root() : this).find(h, k, null);
    }

split()方法源码分析

由于扩容要求太高,所以只进行流程讲解,边看边脑海构思,看完之后就差不多懂了,看过扩容操作的话你会发现和扩容差不多的逻辑

    /**
     * 将树箱中的节点拆分为较低和较高的树箱,  
     * 如果现在太小,就会被树化。 只从resize调用;
     *
     * @param map 当前map结构  
     * @param tab 新数组容器  
     * @param index 当前遍历的下标  
     * @param bit 原数组容器
     */
    final void split(HashMap<K, V> map, Node<K, V>[] tab, int index, int bit) {
        TreeNode<K, V> b = this; //当前头节点     
        //重新连接到lo和hi列表,保持顺序     
        TreeNode<K, V> loHead = null, loTail = null; //lo(当前)头、尾节点(保持秩序)     
        TreeNode<K, V> hiHead = null, hiTail = null; //hi(新)头、尾节点(保持秩序)     
        int lc = 0, hc = 0; //原节点数量、新节点数量     
        for (TreeNode<K, V> e = b, next; e != null; e = next) { //从当前头节点遍历         
            next = (TreeNode<K, V>) e.next; //next节点等于e节点的下一个节点         
            e.next = null; //将遍历节点的下一个节点设为null         
            if ((e.hash & bit) == 0) { //如果等于0,说明还是再这个数组里面             
                if ((e.prev = loTail) == null) //判断当前遍历节点的上一个节点等于loTail节点         
                    loHead = e; //头节点等于当前遍历的节点             
                else //尾节点不为null                 
                    loTail.next = e; //尾节点的下一个节点等于当前遍历的节点(建立双向关系)
                loTail = e; //尾节点等于当前遍历节点,确保每次遍历的节点与上个节点建立关系
                ++lc; //原节点数累加         
            } else { //否则不等于0,说明再新的数组里面             
                if ((e.prev = hiTail) == null) //判断当前遍历节点的上一个节点等于hiTail节点
                    hiHead = e; //头节点等于当前遍历的节点             
                else //尾节点不为null                 
                    hiTail.next = e; //尾节点的下一个节点等于当前遍历的节点(建立双向关系)
                hiTail = e; //尾节点等于当前遍历节点,确保每次遍历的节点与上个节点建立关系
                ++hc; //新节点数累加         
            }
        }
        if (loHead != null) { //(当前)尾节点不为空         
            if (lc <= UNTREEIFY_THRESHOLD) //原节点数小于等于6            
                tab[index] = loHead.untreeify(map); //解除树结构后再赋值给数组下标
            else { //否则原节点数大于6             
                tab[index] = loHead; //数组下标等于重新建立连接后的节点             
                if (hiHead != null) //判断hiHead节点不等于null,等于null说明还是原来的树结构
                    loHead.treeify(tab); //重新开始建立树形化关系         
            }
        }
        if (hiHead != null) { //(新)尾节点不为空         
            if (hc <= UNTREEIFY_THRESHOLD) //新节点数小于等于6     
            //解除树结构后再赋值给新数组下标(当前遍历下标+原数组容器容量,反正不会超过新数组长度(2倍扩容))
                tab[index + bit] = hiHead.untreeify(map);   
            else { //否则新节点数大于6             
                tab[index + bit] = hiHead; //新数组下标等于重新建立连接后的新节点      
                //判断loHead节点不等于null,等于null说明还是原来的树结构,只是换了个位置       
                if (loHead != null) 
                    hiHead.treeify(tab); //重新开始建立树形化关系         
            }
        }
    }

remove()方法源码分析

    //如果该映射存在,则从该映射中移除指定键的映射
    public V remove(Object key) {
        Node<K,V> e;
        //返回删除节点后的节点元素如果等于null返回null否则返回节点的value值     
        return (e = removeNode(hash(key), key, null, false, true)) == null ? 
        null : e.value;
    }

    //删除节点 matchValue=如果为true只移除值相等的情况, movable=如果为false,移除时不要移动其他节点
    final Node<K,V> removeNode(int hash, Object key, Object value, 
    boolean matchValue, boolean movable) {
        Node<K,V>[] tab;Node<K,V> p;int n, index;
        //判断数组不等于null且长度大于0且计算后的数组位置节点赋值p节点不等于null         
        if ((tab = table) != null && (n = tab.length) > 0 && 
        (p = tab[index = (n - 1) & hash]) != null) { 
        Node<K,V> node = null, e; K k; V v;
        //如果删除的key值和p节点的第一个节点相同
            if (p.hash == hash && 
            ((k = p.key) == key || (key != null && key.equals(k)))) 
                node = p; //node节点等于p节点         
            else if ((e = p.next) != null) { //p节点的下一个节点赋值e节点不等于null
                if (p instanceof TreeNode) //如果p节点是树形化节点   
                    //从树节点中查找节点赋值给node节点                           
                    node = ((TreeNode<K,V>) p).getTreeNode(hash, key); 
                else { //否则p节点不是树形化节点                 
                    do {
                        if (e.hash == hash && 
                        ((k = e.key) == key || 
                        (key != null && key.equals(k)))) { //判断e节点和删除节点是否相同    
                            node = e; //node节点等于e节点                         
                            break; //结束                     
                        }
                        p = e; //p节点等于e节点                
                    } while ((e = e.next) != null); //e节点等于e节点的下一个节点不等于null
                }
            }
            //如果node节点不等null且matchValue等于false或value和node的value相等         
            if (node != null && (!matchValue || (v = node.value) == value || 
            (value != null && value.equals(v)))) {
                if (node instanceof TreeNode) //node如果是树节点       
                    //进行树形化删除          
                    ((TreeNode<K, V>) node).removeTreeNode(this, tab, movable); 
                else if (node == p) //如果node等于p节点                
                    tab[index] = node.next; //数组下标元素等于node节点的下一个节点             
                else //否则不是树节点和p节点                 
                    p.next = node.next; //p节点的下一个节点指向node节点的下一个节点             
                ++modCount; //操作次数加一             
                --size; //元素大小减一             
                afterNodeRemoval(node);
                return node; //返回删除的node元素         
            }
        }
        return null; //没找到存在节点,返回null 
    }
案例讲解

在这里插入图片描述
在这里插入图片描述

removeNode()方法:小结
  • 删除链表节点比较简单,主要就是通过删除的key值计算出数组位置的元素,然后从链表节点中找到删除节点的位置,如果是树节点就行树结构查找,当然删除也是进行树结构删除,其他情况如果删除节点是头节点,数组的头节点变成下一个节点,否则将删除节点的上一个节点和下一个节点建立连接,脱离删除节点。

removeTreeNode()方法源码分析

    //删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
    final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                              boolean movable) {
        int n;
        if (tab == null || (n = tab.length) == 0) //如果数组等于null或者数组长度等于0
            return;//结束
        int index = (n - 1) & hash; //通过删除元素的hash值计算下标
        //数组下标第一个元素赋值为first节点和root节点
        TreeNode<K, V> first = (TreeNode<K, V>) tab[index], root = first, rl;
        //获取当前节点的下一个节点和上一个节点
        TreeNode<K, V> succ = (TreeNode<K, V>) next, pred = prev;
        if (pred == null) //判断pred节点等于null
            //数组的第一个节点等于first节点等于下一个节点
            tab[index] = first = succ;
        else //否则不等于null
            pred.next = succ; //pred节点的下一个节点等于succ节点
        if (succ != null) //判断succ节点不等于null
            succ.prev = pred; //判断succ的上一个节点重新指向pred节点
        if (first == null) //判断first节点等于null
            return; //返回
        if (root.parent != null) //判断roor节点的父节点不等于null
            root = root.root(); //root节点等于从root节点开始往上继续找最终的root节点
        //判断root节点等于null或者右、左节点和左节点的左子节点等于null
        if (root == null || root.right == null ||
                (rl = root.left) == null || rl.left == null) {
            tab[index] = first.untreeify(map); //解除树结构
            return; //返回
        }
        //p节点=当前节点、pl节点=左子节点、pr=右子节点、replacement=替换节点
        TreeNode<K, V> p = this, pl = left, pr = right, replacement;
        if (pl != null && pr != null) { //判断pl、pr节点不等于null
            TreeNode<K, V> s = pr, sl; //pr节点赋值给s节点
            while ((sl = s.left) != null) //判断sl节点等于s节点的左子节点不等于null
                s = sl; //s节点等于sl节点
            boolean c = s.red;
            s.red = p.red;
            p.red = c; //s节点的颜色等于p节点的颜色,p节点的颜色等于s节点的颜色
            TreeNode<K, V> sr = s.right; //sr节点等于s节点的右子节点
            TreeNode<K, V> pp = p.parent; //pp节点等于p节点的父节点
            if (s == pr) { //s节点等于pr节点
                p.parent = s; //p节点的父节点等于s节点
                s.right = p; //s节点的右子节点等于p节点
            } else { //否则不相等
                TreeNode<K, V> sp = s.parent; //获取s节点的父节点
                if ((p.parent = sp) != null) { //判断p节点的父节点等于sp节点不等于null
                    if (s == sp.left) //s节点等于sp节点的左子节点
                        sp.left = p; //sp节点的左子节点等于p节点
                    else //否则为右子节点
                        sp.right = p; //sp节点的右子节点等于p节点
                }
                if ((s.right = pr) != null) //s节点的右子节点等于pr节点不等于null
                    pr.parent = s; //pr节点的父节点等于s节点
            }
            p.left = null; //将p节点的左子节点设为null
            if ((p.right = sr) != null) //判断p节点的右子节点等于sr节点不等于null
                sr.parent = p; //sr节点的父节点等于p节点
            if ((s.left = pl) != null) //判断s节点的左子节点等于pl节点不等于null
                pl.parent = s; //pl节点的父节点等于s节点
            if ((s.parent = pp) == null) //判断s节点的父节点等于pp节点等于null
                root = s; //root节点等于s节点
            else if (p == pp.left) //判断p节点等于pp节点的左子节点
                pp.left = s; //pp节点的左子节点等于s节点
            else //判断p节点等于pp节点的右子节点
                pp.right = s; //pp节点的右子节点等于s节点
            if (sr != null) //sr节点不等于null
                replacement = sr; //替换节点等于sr节点
            else //否则sr节点等于null
                replacement = p; //替换节点等于p节点
        }
        else if (pl != null) //判断pl节点不等于null
            replacement = pl; //替换节点等于pl节点
        else if (pr != null) //判断pr节点不等于null
            replacement = pr; //替换节点等于pr节点
        else //否则左右节点都为null
            replacement = p; //替换节点等于p节点
        if (replacement != p) { //判断替换节点不等于p节点
            //pp节点等于替换节点的父节点等于p节点的父节点
            TreeNode<K, V> pp = replacement.parent = p.parent; 
            if (pp == null) //判断pp节点等于null
                root = replacement; //root节点等于替换节点
            else if (p == pp.left) //p节点等于pp节点的左子节点
                pp.left = replacement; //pp的左子节点等于替换节点
            else //否则pp节点不等于null且p节点为右子节点
                pp.right = replacement; //pp节点的右子节点为替换节点
            p.left = p.right = p.parent = null; //p节点的左、右、父节点设为null
        }
        //p节点为红色,r节点等于root节点否则等于删除平衡后的节点
        TreeNode<K, V> r = p.red ? root : balanceDeletion(root, replacement); 
        if (replacement == p) {  //判断替换节点等于p节点
            TreeNode<K, V> pp = p.parent; //pp节点等于p节点的父节点
            p.parent = null; //p节点的父节点等于null
            if (pp != null) { //判断pp节点不等于null
                if (p == pp.left) //判断p节点等于pp节点的左子节点
                    pp.left = null; //pp节点的左节点设为null
                else if (p == pp.right) //判断p节点等于pp节点的右子节点
                    pp.right = null; //pp节点的右节点设为null
            }
        }
        if (movable) //movable等于ture
            moveRootToFront(tab, r); //删除移动
    }
案例讲解

将removeTreeNode()方法切割为三个方面进行讲解。

    //删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
    final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                              boolean movable) {
        int n;
        if (tab == null || (n = tab.length) == 0) //如果数组等于null或者数组长度等于0
            return;//结束
        int index = (n - 1) & hash; //通过删除元素的hash值计算下标
        //数组下标第一个元素赋值为first节点和root节点
        TreeNode<K, V> first = (TreeNode<K, V>) tab[index], root = first, rl;
        //获取当前节点的下一个节点和上一个节点
        TreeNode<K, V> succ = (TreeNode<K, V>) next, pred = prev;
        if (pred == null) //判断pred节点等于null
            //数组的第一个节点等于first节点等于下一个节点
            tab[index] = first = succ;
        else //否则不等于null
            pred.next = succ; //pred节点的下一个节点等于succ节点
        if (succ != null) //判断succ节点不等于null
            succ.prev = pred; //判断succ的上一个节点重新指向pred节点
        if (first == null) //判断first节点等于null
            return; //返回
        if (root.parent != null) //判断roor节点的父节点不等于null
            root = root.root(); //root节点等于从root节点开始往上继续找最终的root节点
        //判断root节点等于null或者右、左节点和左节点的左子节点等于null
        if (root == null || root.right == null ||
                (rl = root.left) == null || rl.left == null) {
            tab[index] = first.untreeify(map); //解除树结构
            return; //返回
        }
        //省略部分代码...
    }

在这里插入图片描述

    //删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
    final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                              boolean movable) {
        //省略部分代码
        //p节点=当前节点、pl节点=左子节点、pr=右子节点、replacement=替换节点
        TreeNode<K, V> p = this, pl = left, pr = right, replacement;
        if (pl != null && pr != null) { //判断pl、pr节点不等于null
            TreeNode<K, V> s = pr, sl; //pr节点赋值给s节点
            while ((sl = s.left) != null) //判断sl节点等于s节点的左子节点不等于null
                s = sl; //s节点等于sl节点
            boolean c = s.red;
            s.red = p.red;
            p.red = c; //s节点的颜色等于p节点的颜色,p节点的颜色等于s节点的颜色
            TreeNode<K, V> sr = s.right; //sr节点等于s节点的右子节点
            TreeNode<K, V> pp = p.parent; //pp节点等于p节点的父节点
            if (s == pr) { //s节点等于pr节点
                p.parent = s; //p节点的父节点等于s节点
                s.right = p; //s节点的右子节点等于p节点
            } else { //否则不相等
                TreeNode<K, V> sp = s.parent; //获取s节点的父节点
                if ((p.parent = sp) != null) { //判断p节点的父节点等于sp节点不等于null
                    if (s == sp.left) //s节点等于sp节点的左子节点
                        sp.left = p; //sp节点的左子节点等于p节点
                    else //否则为右子节点
                        sp.right = p; //sp节点的右子节点等于p节点
                }
                if ((s.right = pr) != null) //s节点的右子节点等于pr节点不等于null
                    pr.parent = s; //pr节点的父节点等于s节点
            }
            p.left = null; //将p节点的左子节点设为null
            if ((p.right = sr) != null) //判断p节点的右子节点等于sr节点不等于null
                sr.parent = p; //sr节点的父节点等于p节点
            if ((s.left = pl) != null) //判断s节点的左子节点等于pl节点不等于null
                pl.parent = s; //pl节点的父节点等于s节点
            if ((s.parent = pp) == null) //判断s节点的父节点等于pp节点等于null
                root = s; //root节点等于s节点
            else if (p == pp.left) //判断p节点等于pp节点的左子节点
                pp.left = s; //pp节点的左子节点等于s节点
            else //判断p节点等于pp节点的右子节点
                pp.right = s; //pp节点的右子节点等于s节点
            if (sr != null) //sr节点不等于null
                replacement = sr; //替换节点等于sr节点
            else //否则sr节点等于null
                replacement = p; //替换节点等于p节点
        }
        //省略部分代码...
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

    //删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
    final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                              boolean movable) {
        //省略部分代码
        else if (pl != null) //判断pl节点不等于null
            replacement = pl; //替换节点等于pl节点
        else if (pr != null) //判断pr节点不等于null
            replacement = pr; //替换节点等于pr节点
        else //否则左右节点都为null
            replacement = p; //替换节点等于p节点
        if (replacement != p) { //判断替换节点不等于p节点
            //pp节点等于替换节点的父节点等于p节点的父节点
            TreeNode<K, V> pp = replacement.parent = p.parent; 
            if (pp == null) //判断pp节点等于null
                root = replacement; //root节点等于替换节点
            else if (p == pp.left) //p节点等于pp节点的左子节点
                pp.left = replacement; //pp的左子节点等于替换节点
            else //否则pp节点不等于null且p节点为右子节点
                pp.right = replacement; //pp节点的右子节点为替换节点
            p.left = p.right = p.parent = null; //p节点的左、右、父节点设为null
        }
        //p节点为红色,r节点等于root节点否则等于删除平衡后的节点
        TreeNode<K, V> r = p.red ? root : balanceDeletion(root, replacement); 
        if (replacement == p) {  //判断替换节点等于p节点
            TreeNode<K, V> pp = p.parent; //pp节点等于p节点的父节点
            p.parent = null; //p节点的父节点等于null
            if (pp != null) { //判断pp节点不等于null
                if (p == pp.left) //判断p节点等于pp节点的左子节点
                    pp.left = null; //pp节点的左节点设为null
                else if (p == pp.right) //判断p节点等于pp节点的右子节点
                    pp.right = null; //pp节点的右节点设为null
            }
        }
        if (movable) //movable等于ture
            moveRootToFront(tab, r); //删除移动
    }

在这里插入图片描述

balanceDeletion()方法源码分析

    //平衡删除
    static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, 
                                                 TreeNode<K,V> x) {
        //xp=x节点的父节点、xpl=xp节点的左子节点、xpr=xp节点的右子节点
        for (TreeNode<K,V> xp, xpl, xpr; ; ) {
            if (x == null || x == root) //判断x节点等于null获取x节点等于root节点
                return root; //返回root节点
            else if ((xp = x.parent) == null) { //判断x节点的父节点赋值给xp节点等于null
                x.red = false; //x节点设为黑色
                return x; //返回x节点
            } else if (x.red) { //判断x节点红色
                x.red = false; //将x节点设为黑色
                return root; //返回root节点
            } else if ((xpl = xp.left) == x) { //判断xpl节点等于xp节点的左子节点等于x节点
                //xpr等于xp节点的右子节点不等于null且红色
                if ((xpr = xp.right) != null && xpr.red) { 
                    xpr.red = false; //xpr节点设为黑色
                    xp.red = true; //xp节点设为红色
                    root = rotateLeft(root, xp); //从xp节点开始左旋
                    //xp等于x节点的父节点等于null,xpr等于null否则等于xp节点的右子节点
                    xpr = (xp = x.parent) == null ? null : xp.right;
                }
                if (xpr == null) //判断xpr节点等于null
                    x = xp; //x节点等于xp节点
                else //否则xpr节点不等于null
                     //获取xpr节点的左子节点、右子节点
                    TreeNode<K,V> sl = xpr.left, sr = xpr.right; 
                //判断sr节点等于null或者黑色且sl等于null或者黑色
                if ((sr == null || !sr.red) &&
                        (sl == null || !sl.red)) {
                    xpr.red = true; //xpr节点设为红色
                    x = xp; //x节点等于xp节点
                } else { //否则sr节点或者sl节点不等于null
                    if (sr == null || !sr.red) { //sr节点等于null或者黑色
                        if (sl != null) //sl节点不等于null
                            sl.red = false; //sl节点设为黑色
                        xpr.red = true; //xpr节点设为红色
                        root = rotateRight(root, xpr); //右旋
                        //xp等于x节点的父节点等于null,xpr等于null否则等于xp节点的右子节点
                        xpr = (xp = x.parent) == null ? null : xp.right;
                    }
                    if (xpr != null) { //判断xpr不等于null
                        //xp节点等于null,xpr节点为黑色,否则为xp节点颜色
                        xpr.red = (xp == null) ? false : xp.red;
                        if ((sr = xpr.right) != null) //sr等于xpr节点的右子节点不等于null
                            sr.red = false; //sr节点设为黑色
                    }
                    if (xp != null) { //判断xp节点不等于null
                        xp.red = false; //xp节点设为黑色
                        root = rotateLeft(root, xp); //左旋
                    }
                    x = root; //x节点等于root节点
                }
            } else { //否则x节点是xp节点的右子节点
                if (xpl != null && xpl.red) { //判断xpl节点不等于null且xpl节点等于红色
                    xpl.red = false; //xpl节点设为黑色
                    xp.red = true; //xp节点设为红色
                    root = rotateRight(root, xp); //右旋
                    //xp等于x节点的父节点等于null,xpl等于null否则等于xp节点的左子节点
                    xpl = (xp = x.parent) == null ? null : xp.left;
                }
                if (xpl == null) //判断xpl节点等于null
                    x = xp; //x节点等于xp节点
                else { //否则xpl节点不等于null
                    //获取xpl节点的左子节点、右子节点
                    TreeNode<K,V> sl = xpl.left, sr = xpl.right;
                    //判断sr节点等于null或者黑色且sl等于null或者黑色
                    if ((sl == null || !sl.red) &&
                            (sr == null || !sr.red)) {
                        xpl.red = true; //xpr节点设为红色
                        x = xp; //x节点等于xp节点
                    } else { //否则sr节点或者sl节点不等于null
                        if (sl == null || !sl.red) { //sr节点等于null或者黑色
                            if (sr != null) //sl节点不等于null
                                sr.red = false; //sl节点设为黑色
                            xpl.red = true; //xpr节点设为红色
                            root = rotateLeft(root, xpl); //左旋
                            //xp等于x节点的父节点等于null,xpl等于null否则等于xp节点的左子节点
                            xpl = (xp = x.parent) == null ? null : xp.left;
                        }
                        if (xpl != null) { //判断xpl不等于null
                            //xp节点等于null,xpl节点为黑色,否则为xp节点颜色
                            xpl.red = (xp == null) ? false : xp.red;
                            if ((sl = xpl.left) != null) //sr等于xpl节点的右子节点不等于null
                                sl.red = false; //sr节点设为黑色
                        }
                        if (xp != null) { //判断xp节点不等于null
                            xp.red = false; //xp节点设为黑色
                            root = rotateRight(root, xp); //右旋
                        }
                        x = root; //x节点等于root节点
                    }
                }
            }
        }
    }
案例讲解

尾部节点的平衡操作

    //平衡删除
    static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, 
                                                 TreeNode<K,V> x) {
        //xp=x节点的父节点、xpl=xp节点的左子节点、xpr=xp节点的右子节点
        for (TreeNode<K,V> xp, xpl, xpr; ; ) {
            if (x == null || x == root) //判断x节点等于null获取x节点等于root节点
                return root; //返回root节点
            else if ((xp = x.parent) == null) { //判断x节点的父节点赋值给xp节点等于null
                x.red = false; //x节点设为黑色
                return x; //返回x节点
            } else if (x.red) { //判断x节点红色
                x.red = false; //将x节点设为黑色
                return root; //返回root节点
            } else if ((xpl = xp.left) == x) { //判断xpl节点等于xp节点的左子节点等于x节点
                //xpr等于xp节点的右子节点不等于null且红色
                if ((xpr = xp.right) != null && xpr.red) { 
                    xpr.red = false; //xpr节点设为黑色
                    xp.red = true; //xp节点设为红色
                    root = rotateLeft(root, xp); //从xp节点开始左旋
                    //xp等于x节点的父节点等于null,xpr等于null否则等于xp节点的右子节点
                    xpr = (xp = x.parent) == null ? null : xp.right;
                }
                if (xpr == null) //判断xpr节点等于null
                    x = xp; //x节点等于xp节点
                else //否则xpr节点不等于null
                     //获取xpr节点的左子节点、右子节点
                    TreeNode<K,V> sl = xpr.left, sr = xpr.right; 
                //判断sr节点等于null或者黑色且sl等于null或者黑色
                if ((sr == null || !sr.red) &&
                        (sl == null || !sl.red)) {
                    xpr.red = true; //xpr节点设为红色
                    x = xp; //x节点等于xp节点
                }
                //省略部分代码...
            }
        }
    }

在这里插入图片描述
左旋和右旋在这里就不在详细讲解了,相信你们一眼就知道结果了在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

    //删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
    final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                              boolean movable) {
        //省略部分代码
        //p节点为红色,r节点等于root节点否则等于删除平衡后的节点
        TreeNode<K, V> r = p.red ? root : balanceDeletion(root, replacement); 
        if (replacement == p) {  //判断替换节点等于p节点
            TreeNode<K, V> pp = p.parent; //pp节点等于p节点的父节点
            p.parent = null; //p节点的父节点等于null
            if (pp != null) { //判断pp节点不等于null
                if (p == pp.left) //判断p节点等于pp节点的左子节点
                    pp.left = null; //pp节点的左节点设为null
                else if (p == pp.right) //判断p节点等于pp节点的右子节点
                    pp.right = null; //pp节点的右节点设为null
            }
        }
        if (movable) //movable等于ture
            moveRootToFront(tab, r); //删除移动
    }

在这里插入图片描述

    /**
     * 确保给定的根是其bin的第一个节点。 (移动root节点到前面)
     */
    static <K, V> void moveRootToFront(Node<K, V>[] tab, TreeNode<K, V> root) {
        int n; //数组长度    
        if (root != null && tab != null && (n = tab.length) > 0) { //判断root节点不等于null且数组不等于null数组长度大于0       
            int index = (n - 1) & root.hash; //通过root的hash值计算下标        
            TreeNode<K, V> first = (TreeNode<K, V>) tab[index]; //获取下标第一个节点        
            if (root != first) { //判断新的root节点不等于第一个节点            
                Node<K, V> rn; //root节点的下一个节点            
                tab[index] = root; //数组下标替换为root节点            
                TreeNode<K, V> rp = root.prev; //root节点的上一个节点            
                if ((rn = root.next) != null) //判断root节点的下一个节点不等于null               
                    ((TreeNode<K, V>) rn).prev = rp; //rn节点的上一个节点等于rp节点           
                if (rp != null) //判断rp节点不等于null                
                    rp.next = rn; //rp节点的下一个节点等于rn节点(建立上下关系)           
                if (first != null) //判断first节点不等于null                
                    first.prev = root; //first节点的上一个节点等于root           
                root.next = first; //root节点的下一个节点等于first            
                root.prev = null; //root节点的上一个节点等于null        
            }
            assert checkInvariants(root); //检查根节点是否满足红黑树节点规则    
        }
    }

在这里插入图片描述
在这里插入图片描述

再来讲解,中间节点的平衡操作

    //删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
    final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                              boolean movable) {
        int n;
        if (tab == null || (n = tab.length) == 0) //如果数组等于null或者数组长度等于0
            return;//结束
        int index = (n - 1) & hash; //通过删除元素的hash值计算下标
        //数组下标第一个元素赋值为first节点和root节点
        TreeNode<K, V> first = (TreeNode<K, V>) tab[index], root = first, rl;
        //获取当前节点的下一个节点和上一个节点
        TreeNode<K, V> succ = (TreeNode<K, V>) next, pred = prev;
        if (pred == null) //判断pred节点等于null
            //数组的第一个节点等于first节点等于下一个节点
            tab[index] = first = succ;
        else //否则不等于null
            pred.next = succ; //pred节点的下一个节点等于succ节点
        if (succ != null) //判断succ节点不等于null
            succ.prev = pred; //判断succ的上一个节点重新指向pred节点
        if (first == null) //判断first节点等于null
            return; //返回
        if (root.parent != null) //判断roor节点的父节点不等于null
            root = root.root(); //root节点等于从root节点开始往上继续找最终的root节点
        //判断root节点等于null或者右、左节点和左节点的左子节点等于null
        if (root == null || root.right == null ||
                (rl = root.left) == null || rl.left == null) {
            tab[index] = first.untreeify(map); //解除树结构
            return; //返回
        }
        //省略部分代码...
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

    //删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
    final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                              boolean movable) {
        int n;
        if (tab == null || (n = tab.length) == 0) //如果数组等于null或者数组长度等于0
            return;//结束
        int index = (n - 1) & hash; //通过删除元素的hash值计算下标
        //数组下标第一个元素赋值为first节点和root节点
        TreeNode<K, V> first = (TreeNode<K, V>) tab[index], root = first, rl;
        //获取当前节点的下一个节点和上一个节点
        TreeNode<K, V> succ = (TreeNode<K, V>) next, pred = prev;
        //省略部分代码
        //判断root节点等于null或者右、左节点和左节点的左子节点等于null
        if (root == null || root.right == null ||
                (rl = root.left) == null || rl.left == null) {
            tab[index] = first.untreeify(map); //解除树结构
            return; //返回
        }
        //省略部分代码...
    }

在这里插入图片描述

untreeify()方法源码分析

    //解除树形结构 
    final Node<K, V> untreeify(HashMap<K, V> map) {
        //hd=头节点、tl=引用节点     
        Node<K, V> hd = null, tl = null;
        for (Node<K, V> q = this; q != null; q = q.next) { //从当前节点开始遍历         
            Node<K, V> p = map.replacementNode(q, null); //讲节点改为普通节点         
            if (tl == null) //判断tl节点等于null             
                hd = p; //hd节点等于p节点         
            else //否则tl节点不等于null             
                tl.next = p; //tl节点的下一个节点等于p         
            tl = p; //tl节点等于p节点     
        }
        return hd; //返回hd节点 
    }
案例讲解

在这里插入图片描述

removeTreeNode()方法:小结
  • 删除树型节点主要分为三个步骤:第一个步骤是维护链表结构、第二个步骤是维护红黑树结构、第三个步骤是解除关系
  • 维护链表结构。和链表结构删除一样,上一个节点为空,数组的头节点变成下一个元素,否则上一个节点和下一个节点建立联系,脱离出删除节点。还有一种情况就是树节点的根节点等于null或者左右子节点等于null的情况,就会进行解除树型化结构变成链表结构(在扩容的时候也会进行链表长度判断,不达到7的都会变成链表结构)。
  • 维护红黑树结构。链表结构维护好,再来维护红黑树结构,如果父节点和子节点不等于null,就会将父节点和子节点建立联系,直到删除节点变成最后一个子节点,单向切断子节点和父节点
  • 解除关系。两种情况,一种是替换节点不是删除节点,这种情况先将替换节点改为删除节点然后在解除左、右、父节点的联系;另一种是替换节点等于删除节点,再维护红黑树的时候已经单向的解除了子节点与父节点的关系,然后从父节点角度,来解除与子节点的关系,这样就完成了双向关系的解除。你会发现解除关系都会将删除节点挪到最后的子节点去做删除

然后接着讲解删除平衡的剩余部分代码流程

    //平衡删除
    static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, 
                                                 TreeNode<K,V> x) {
        //xp=x节点的父节点、xpl=xp节点的左子节点、xpr=xp节点的右子节点
        for (TreeNode<K,V> xp, xpl, xpr; ; ) {
            if (x == null || x == root) //判断x节点等于null获取x节点等于root节点
                return root; //返回root节点
            else if ((xp = x.parent) == null) { //判断x节点的父节点赋值给xp节点等于null
                x.red = false; //x节点设为黑色
                return x; //返回x节点
            } else if (x.red) { //判断x节点红色
                x.red = false; //将x节点设为黑色
                return root; //返回root节点
            } else if ((xpl = xp.left) == x) { //判断xpl节点等于xp节点的左子节点等于x节点
                //xpr等于xp节点的右子节点不等于null且红色
                if ((xpr = xp.right) != null && xpr.red) { 
                    xpr.red = false; //xpr节点设为黑色
                    xp.red = true; //xp节点设为红色
                    root = rotateLeft(root, xp); //从xp节点开始左旋
                    //xp等于x节点的父节点等于null,xpr等于null否则等于xp节点的右子节点
                    xpr = (xp = x.parent) == null ? null : xp.right;
                }
                if (xpr == null) //判断xpr节点等于null
                    x = xp; //x节点等于xp节点
                else //否则xpr节点不等于null
                     //获取xpr节点的左子节点、右子节点
                    TreeNode<K,V> sl = xpr.left, sr = xpr.right; 
                //判断sr节点等于null或者黑色且sl等于null或者黑色
                if ((sr == null || !sr.red) &&
                        (sl == null || !sl.red)) {
                    xpr.red = true; //xpr节点设为红色
                    x = xp; //x节点等于xp节点
                } else { //否则sr节点或者sl节点不等于null
                    if (sr == null || !sr.red) { //sr节点等于null或者黑色
                        if (sl != null) //sl节点不等于null
                            sl.red = false; //sl节点设为黑色
                        xpr.red = true; //xpr节点设为红色
                        root = rotateRight(root, xpr); //右旋
                        //xp等于x节点的父节点等于null,xpr等于null否则等于xp节点的右子节点
                        xpr = (xp = x.parent) == null ? null : xp.right;
                    }
                    if (xpr != null) { //判断xpr不等于null
                        //xp节点等于null,xpr节点为黑色,否则为xp节点颜色
                        xpr.red = (xp == null) ? false : xp.red;
                        if ((sr = xpr.right) != null) //sr等于xpr节点的右子节点不等于null
                            sr.red = false; //sr节点设为黑色
                    }
                    if (xp != null) { //判断xp节点不等于null
                        xp.red = false; //xp节点设为黑色
                        root = rotateLeft(root, xp); //左旋
                    }
                    x = root; //x节点等于root节点
                }
            } 
            //省略部分代码...
            }
        }
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
上面讲解了左子节点的删除平衡,再来讲解x节点是右子节点的情况

    //平衡删除
    static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, 
                                                 TreeNode<K,V> x) {
        //xp=x节点的父节点、xpl=xp节点的左子节点、xpr=xp节点的右子节点
        for (TreeNode<K,V> xp, xpl, xpr; ; ) {
            if (x == null || x == root) //判断x节点等于null获取x节点等于root节点
                return root; //返回root节点
            else if ((xp = x.parent) == null) { //判断x节点的父节点赋值给xp节点等于null
                x.red = false; //x节点设为黑色
                return x; //返回x节点
            } else if (x.red) { //判断x节点红色
                x.red = false; //将x节点设为黑色
                return root; //返回root节点
            } //省略部分代码...
            else { //否则x节点是xp节点的右子节点
                if (xpl != null && xpl.red) { //判断xpl节点不等于null且xpl节点等于红色
                    xpl.red = false; //xpl节点设为黑色
                    xp.red = true; //xp节点设为红色
                    root = rotateRight(root, xp); //右旋
                    //xp等于x节点的父节点等于null,xpl等于null否则等于xp节点的左子节点
                    xpl = (xp = x.parent) == null ? null : xp.left;
                }
                if (xpl == null) //判断xpl节点等于null
                    x = xp; //x节点等于xp节点
                else { //否则xpl节点不等于null
                    //获取xpl节点的左子节点、右子节点
                    TreeNode<K,V> sl = xpl.left, sr = xpl.right;
                    //判断sr节点等于null或者黑色且sl等于null或者黑色
                    if ((sl == null || !sl.red) &&
                            (sr == null || !sr.red)) {
                        xpl.red = true; //xpr节点设为红色
                        x = xp; //x节点等于xp节点
                    } else { //否则sr节点或者sl节点不等于null
                        if (sl == null || !sl.red) { //sr节点等于null或者黑色
                            if (sr != null) //sl节点不等于null
                                sr.red = false; //sl节点设为黑色
                            xpl.red = true; //xpr节点设为红色
                            root = rotateLeft(root, xpl); //左旋
                            //xp等于x节点的父节点等于null,xpl等于null否则等于xp节点的左子节点
                            xpl = (xp = x.parent) == null ? null : xp.left;
                        }
                        if (xpl != null) { //判断xpl不等于null
                            //xp节点等于null,xpl节点为黑色,否则为xp节点颜色
                            xpl.red = (xp == null) ? false : xp.red;
                            if ((sl = xpl.left) != null) //sr等于xpl节点的右子节点不等于null
                                sl.red = false; //sr节点设为黑色
                        }
                        if (xp != null) { //判断xp节点不等于null
                            xp.red = false; //xp节点设为黑色
                            root = rotateRight(root, xp); //右旋
                        }
                        x = root; //x节点等于root节点
                    }
                }
            }
        }
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

balanceDeletion()方法:小结
  • 删除平衡操作,主要为了平衡过程中,将删除节点移动到最后一个节点且保持结构、颜色平衡,再进行删除。

get()方法源码分析

    //通过key获取value
    public V get(Object key) {
        Node<K, V> e;//e节点
        //调用getNode方法后,判断e节点不等于null,返回对于的value,否则返回null     
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    //获取节点
    final Node<K,V> getNode(int hash, Object key) {
        //tab=数组、first=第一个节点、e=下一个节点、n=数组长度、k=key
        Node<K,V>[] tab;Node<K,V> first, e;int n;K k;
        //判断table不等于null且长度大于0,first节点不等于null
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) { 
            //判断first节点的hash值、key等于传入的hash值、key
            if (first.hash == hash && 
            ((k = first.key) == key || (key != null && key.equals(k)))) 
                return first; //返回first节点
            if ((e = first.next) != null) { //判断e节点等于first节点的下一个节点不等于null
                if (first instanceof TreeNode) //first节点等于树形化节点
                //调用树形化获取方法
                    return ((TreeNode<K, V>) first).getTreeNode(hash, key); 
                do { //不是树形化节点且下一个节点不等于null,进入循环
                //判断e节点等于first节点的下一个节点不等于null
                    if (e.hash == hash && 
                            ((k = e.key) == key || (key != null && key.equals(k)))) 
                        return e; //返回e节点
                        //判断e节点等于e节点的下一个节点不等于null,进入下次循环
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

在这里插入图片描述
在这里插入图片描述

    //寻找左右子节点是否有相同节点
    final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
        TreeNode<K,V> p = this; //p=当前节点    
        do {
            int ph, dir;
            K pk; //ph=p节点的hash值、dir=存放位置遍历、p=p节点key值        
            TreeNode<K,V> pl = p.left, pr = p.right, q; //pl=p节点的左子节点、 pr=p节点的右子节点、q=相同节点        
            if ((ph = p.hash) > h) //p节点hash值大于h            
                p = pl; //p节点等于pl节点        
            else if (ph < h) // p节点hash值小于h            
                p = pr; // p节点等于pr节点       
            else if ((pk = p.key) == k || (k != null && k.equals(pk))) //key值相同           
                return p; //返回p节点        
            else if (pl == null) //pl节点等于null           
                p = pr; //p节点等于pr节点        
            else if (pr == null) //pr节点等于null           
                p = pl; //p节点等于pl节点        
                //否则不满足前几种情况,kc类似类不等于null(有类似类)或者类比较不等于0(两个类比较不相等)(不做讲解,理解即可)   
            else if ((kc != null || (kc = comparableClassFor(k)) != null) && 
                    (dir = compareComparables(kc, k, pk)) != 0)         
                p = (dir < 0) ? pl : pr; //dir小于0从p节点等于pl节点否则等于pr节点       
            else if ((q = pr.find(h, k, kc)) != null) //q节点等于pr节点开始匹配且不等于null           
                return q; //返回q节点       
            else //否则          
                p = pl; //p节点等于pl节点    
        } while (p != null); //p节点不等于null,进入循环结构   
        return null; // p节点等于null,节点循环,返回null
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

get()方法:小结
  • 获取节点是比较简单的,主要分为链表获取和树节点获取。
  • 链表获取。从第一个节点开始一直往下遍历,如果有匹配的节点就返回对应的value,否则返回null。
  • 树节点获取。从当前节点遍历,由于有左右子节点区分,所以获取的时候,先判断是从左子节点还是右子节点依次往下遍历判断,如果有匹配节点返回对应的value,否则返回null。

知识扩展

什么是红黑树(为什么称为红黑树)?

红黑树是一种自平衡的二叉查找树,是一种高效的查找树。它是由 Rudolf Bayer 于1972年发明,在当时被称为对称二叉 B 树(symmetric binary B-trees)。红黑树的命名和Xerox PARC也有关系,当时Robert Sedgewick在施乐做访问学者的时候,施乐正好发明出激光彩色打印机,然后觉得这个打印机打出的红色很好看,就用了红色来区分结点的不同,于是就叫红黑树了,后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的红黑树。红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作。

为什么使用的是红黑树而不是其它树?

AVL树和红黑树都是平衡二叉树。AVL树规定:左子树和右子树的高度差不能超过一倍,AVL树是更加严格的平衡,旋转的次数比红黑树要多,因此可以提供更快的查找速度;红黑树节点左右子树高度差长的不会超过两倍,红黑树基本在三次旋转之内达到平衡,这一特性使得红黑树的查询比AVL树慢,但是插入和删除根据不同情况,旋转的次数比AVL树要少,所以插入和删除效率较高。
B树、B+树是一种平衡多路查找树,这两种数据结构利用磁盘页的特性来构建的树结构,这样一次可以把一个磁盘的数据读入内存,减少I/O操作。B+树的结构比B树比红黑树更“矮胖”,所以查询的次数更少,红黑树在数据量不是很多的情况下,数据都会“挤在”一个页里面,这个时候遍历效率就退化成了链表。B树在插入和删除的时候涉及到磁盘页的分裂和合并操作;B+树因为所有的数据都保存在叶子节点,在插入和删除的时候除了B树的基本操作外,还要通过旋转维护叶子节点的顺序,而红黑树基本在三次旋转之内达到平衡,所以插入和删除效率较高。

为什么HashMap的数组长度是2次幂(为什么初始化容器值设为16)?

为了存取高效,要尽量减少碰撞,就是要尽量把数据分配均匀,通过公式(n - 1) & hash 代替取模运算提高效率,具体讲解如下:
在这里插入图片描述
结果显而易见

为什么HashMap的加载因子是0.75?

提高空间利用率和 减少查询成本的折中,主要是泊松分布,0.75的话碰撞最小,加载因子过高,例如为1,虽然减少了空间开销,提高了空间利用率,但同时也增加了查询时间成本;加载因子过低,例如0.5,虽然可以减少查询时间成本,但是空间利用率很低,同时提高了rehash操作的次数。

HashMap什么时候转化为树结构?

当链表长度大于8且数组长度大于64,数组长度不满足64会进行扩容。

为什么当链表长度大于8的时候进行树形化?

根据官方源码注释:如果 hashCode的分布离散良好的话,那么红黑树是很少会被用到的,理想情况下遵循泊松分布,使得各种长度命中的概率非常小,从而转换为红黑树的概率也小

为什么当树长度小于6的时候转换为链表?

当数组元素个数大于阈值时,需要扩容,判断链表长度小于等于6,接触树结构。主要是避免树与链表之间的频繁转换,如果等于7,删除一个元素红黑树就必须退化为链表,增加一个元素就必须树形化,浪费资源,影响性能。6主要起一个过渡作用。转换为链表还有一种情况就是红黑树删除元素的时候,如果树的节点小于3的时候也会转为链表。

章节目录

上一篇:集合底层源码分析之HashMap《上》(三)
下一篇:集合底层源码分析之LinkedHashMap(四)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值