10、JDK1.8HashMap源码分析系列文章(remove、removeNode、removeTreeNode、balanceDeletion)

1、remove(Object key)

        /**
         * 外供方法,用于删除Map中对应key的值
         * @param key 待删除的key
         * @return 删除key对应的value值
         */
        public V remove(Object key) {
            HashMap.Node e;
            return (e = removeNode(hash(key), key, null, false, true)) == null ?
                    null : e.value;
        }

2、removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)

        /**
         * @param hash       key对应的hash值
         * @param key        key
         * @param value      key对应的值
         * @param matchValue 是否需要对值进行匹配操作
         * @param movable    是否将根节点移动到链表顶端
         * @return 待删除的节点
         * @Author muyi
         * @Date 18:01 2020/8/4
         */
        final HashMap.Node removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) {
            // tab-map中存放元素的数组,p-头节点,n-数组的长度,index-索引值
            HashMap.Node[] tab;
            HashMap.Node p;
            int n, index;
            /**
             * 以下三个条件与
             * 1、(tab = table) != null:数组不为空
             * 2、(n = tab.length) > 0:数组长度大于0
             * 3、(p = tab[index = (n - 1) & hash]) != null:头节点元素存在
             */
            if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) {
                // node-存放查找到的元素,e-遍历的中间节点
                HashMap.Node node = null, e;
                K k;
                V v;
                /**
                 * 若头节点key对应的hash值、值相等,说明头节点即是查找的元素,
                 * 如果在这个位置找到删除节点,此时 p == 头节点,如果在红黑树或者链表其他位置找到,此时的p会改变成其他节点
                 */
                if (p.hash == hash &&
                        ((k = p.key) == key || (key != null && key.equals(k))))
                    node = p;
                    // 如果头节点不是删除的元素,则需要在链表或红黑树中依次去查找
                else if ((e = p.next) != null) {
                    // 如果是红黑树节点类型,查找红黑树节点,此时找到,p仍然指向头节点,但 node != p
                    if (p instanceof HashMap.TreeNode)
                        node = ((HashMap.TreeNode) p).getTreeNode(hash, key);
                        // 查找普通节点,此时找到,p不再指向头节点,而是指向node的父节点,node != p
                    else {
                        do {
                            if (e.hash == hash &&
                                    ((k = e.key) == key ||
                                            (key != null && key.equals(k)))) {
                                node = e;
                                break;
                            }
                            // 移动p指针的指向,如果待删除节点找到,保持p指针始终指向待删除节点前驱节点
                            p = e;
                        } while ((e = e.next) != null);
                    }
                }
                /**
                 * 以下两个条件与
                 * 1、node != null:待删除的节点找到
                 * 2、(!matchValue || (v = node.value) == value || (value != null && value.equals(v)))
                 * !matchValue:是否需要匹配value,如果不需要匹配,则直接进行删除操作,如果需要的话,则需要判断value必须相等,才可以进行删除
                 */
                if (node != null && (!matchValue || (v = node.value) == value ||
                        (value != null && value.equals(v)))) {
                    // 如果是树节点,进行红黑树节点删除操作
                    if (node instanceof HashMap.TreeNode)
                        ((HashMap.TreeNode) node).removeTreeNode(this, tab, movable);
                    else if (node == p)
                    /**
                     * 如果待删除的节点是头节点,直接将待删除节点的后继节点置于头节点位置即可,
                     * 这里是单纯的链表节点,只有next指向,没有prev指向
                     */
                        tab[index] = node.next;
                    else
                        p.next = node.next;
                    ++modCount;
                    // 长度减1
                    --size;
                    // ConcurrentHashMap需要的操作
                    afterNodeRemoval(node);
                    // 返回待删除的节点
                    return node;
                }
            }
            // 如果未找到待删除key对应的节点值,返回null
            return null;
        }

3、removeTreeNode(HashMap map, HashMap.Node[] tab, boolean movable)

        /**
         * 这个方法是HashMap.TreeNode的内部方法,调用该方法的节点为待删除节点
         *
         * @param map     删除操作的map
         * @param tab     map存放数据的链表
         * @param movable 是否移动跟节点到头节点
         * @return void
         * @Author muyi
         * @Date 9:34 2020/8/5
         */
        final void removeTreeNode(HashMap map, HashMap.Node[] tab, boolean movable) {
            int n;
            if (tab == null || (n = tab.length) == 0)
                return;
            // 获取索引值
            int index = (n - 1) & hash;
            /**
             * first-头节点,数组存放数据索引位置存在存放的节点值
             * root-根节点,红黑树的根节点,正常情况下二者是相等的
             * rl-root节点的左孩子节点,succ-后节点,pred-前节点
             */
            HashMap.TreeNode first = (HashMap.TreeNode) tab[index], root = first, rl;
            // succ-调用这个方法的节点(待删除节点)的后驱节点,prev-调用这个方法的节点(待删除节点)的前驱节点
            HashMap.TreeNode succ = (HashMap.TreeNode) next, pred = prev;
            /**
             * 维护双向链表(map在红黑树数据存储的过程中,除了维护红黑树之外还对双向链表进行了维护)
             * 从链表中将该节点删除
             * 如果前驱节点为空,说明删除节点是头节点,删除之后,头节点直接指向了删除节点的后继节点
             */
            if (pred == null)
                tab[index] = first = succ;
            else
                pred.next = succ;
            if (succ != null)
                succ.prev = pred;
            // 如果头节点(即根节点)为空,说明该节点删除后,红黑树为空,直接返回
            if (first == null)
                return;
            // 如果父节点不为空,说明删除后,调用root方法重新获取当前树的根节点
            if (root.parent != null)
                root = root.root();
            /**
             * 当以下三个条件任一满足时,当满足红黑树条件时,说明该位置元素的长度少于6(UNTREEIFY_THRESHOLD),需要对该位置元素链表化
             * 1、root == null:根节点为空,树节点数量为0
             * 2、root.right == null:右孩子为空,树节点数量最多为2
             * 3、(rl = root.left) == null || rl.left == null):
             *      (rl = root.left) == null:左孩子为空,树节点数最多为2
             *      rl.left == null:左孩子的左孩子为NULL,树节点数最多为6
             */
            if (root == null || root.right == null ||
                    (rl = root.left) == null || rl.left == null) {
                // 链表化,因为前面对链表节点完成了删除操作,故在这里完成之后直接返回,即可完成节点的删除
                tab[index] = first.untreeify(map);
                return;
            }
            /**
             * p-调用此方法的节点(待删除节点),pl-待删除节点的左子节点,pr-待删除节点的右子节点,replacement-替换节点
             * 以下是对红黑树进行维护
             */
            HashMap.TreeNode p = this, pl = left, pr = right, replacement;
            // 1、删除节点有两个子节点
            if (pl != null && pr != null) {
                // 第一步:找到当前节点的后继节点(注意与后驱节点的区别,值大于当前节点值的最小节点,以右子树为根节点,查找它对用的最左节点)
                HashMap.TreeNode s = pr, sl;
                // 循环在左子树中查找
                while ((sl = s.left) != null) // find successor
                    s = sl;
                // 第二步:交换后继节点和删除节点的颜色,最终的删除是删除后继节点,故平衡是否是以后继节点的颜色来判断的
                boolean c = s.red;
                s.red = p.red;
                p.red = c; // swap colors
                // sr-后继节点的右孩子(后继节点是肯定不存在左孩子的,如果存在的话,那么它肯定不是后继节点)
                HashMap.TreeNode sr = s.right;
                // pp-待删除节点的父节点
                HashMap.TreeNode pp = p.parent;
                // 第三步:修改当前节点和后继节点的父节点
                // 如果后继节点与当前节点的右孩子相等,类似于右孩子只有一个节点
                if (s == pr) { // p was s's direct parent
                    // 交换两个节点的位置,父节点变子节点,子节点变父节点
                    p.parent = s;
                    s.right = p;
                } else {
                    // 如果当前节点的右子树不止一个节点,记录sp-后继节点的父节点
                    HashMap.TreeNode sp = s.parent;
                    // 交换待删除节点和后继节点的的父节点,如果后继节点父节点不为null,指定后继节点父节点的孩子节点
                    if ((p.parent = sp) != null) {
                        // 如果后继节点是其父节点的左孩子,修改父节点左孩子值
                        if (s == sp.left)
                            sp.left = p;
                            // 如果后继节点是其父节点的右孩子,修改父节点右孩子值
                        else
                            sp.right = p;
                    }
                    // 修改后继节点的右孩子值,如果不为null,同时指定其父节点的值
                    if ((s.right = pr) != null)
                        pr.parent = s;
                }
                // 第四步:修改当前节点和后继节点的孩子节点,当前节点现在变成后继节点了,故其左孩子为null.
                p.left = null;
                // 修改当前节点的右孩子值,如果其不为空,同时修改其父节点指向当前节点
                if ((p.right = sr) != null)
                    sr.parent = p;
                // 修改后继节点的左孩子值,如果其不为空,同时修改其父节点指向后继节点
                if ((s.left = pl) != null)
                    pl.parent = s;
                // 修改后继节点的父节点值,如果其为null,说明后继节点现在变成了root节点
                if ((s.parent = pp) == null)
                    root = s;
                    // 当前节点是其父节点的左孩子
                else if (p == pp.left)
                    pp.left = s;
                    // 当前节点是其父节点的右孩子
                else
                    pp.right = s;
                /**
                 * sr-后继节点的右孩子节点(有一个孩子节点),
                 * 如果右孩子节点不为空,删除节点后,替代节点就是其右孩子节点
                 * 如果为空,那么替代节点就是其本身
                 */
                if (sr != null)
                    replacement = sr;
                else
                    replacement = p;
                // 2、删除节点有一个左子节点,左子节点作为替代节点
            } else if (pl != null)
                replacement = pl;
                // 3、删除节点有一个右子节点,右子节点作为替代节点
            else if (pr != null)
                replacement = pr;
                // 4、删除节点没有子节点,直接删除当前节点
            else
                replacement = p;
            /**
             * 如果删除节点存在两个孩子节点,最终与后继节点交换后,删除的节点的位置位于后继节点的位置,那么此时删除节点所处的位置演变成:
             * a、只有一个孩子节点:(replacement = p.right) != p
             * b、没有孩子节点:replacement == p
             * 只有当删除节点与替换节点不相等的时候,才对删除节点进行删除操作
             */
            if (replacement != p) {
                // 从红黑树中将待删除节点(即当前节点移除)
                HashMap.TreeNode pp = replacement.parent = p.parent;
                // 是否为根节点
                if (pp == null)
                    root = replacement;
                    // 其父节点的左子节点
                else if (p == pp.left)
                    pp.left = replacement;
                    // 其父节点的右子节点
                else
                    pp.right = replacement;
                // 节点的指向全部置NULL
                p.left = p.right = p.parent = null;
            }
            /**
             * 如果删除节点的颜色是红色,不会影响整棵树的黑色高度,毋需自平衡,根节点不会变化,如果是黑色,则需要进行自平衡,重新获取根节点
             * 注意:
             * 自平衡的时候 替代节点可能与删除节点相等:replacement == p
             * 自平衡的时候 替代节点可能与删除节点不相等:replacement != p
             */
            HashMap.TreeNode r = p.red ? root : balanceDeletion(root, replacement);
            /**
             * 当 replacement == p 时,是先进行了红黑树的进行了平衡操作,再将这个节点从红黑树中移除
             * 这个地方我也没明白原理是什么,但是我按照这个步骤去走了一遍,确实这样操作来完成平衡,如果有哪位大神明白的,麻烦指导一下,谢谢!
             */
            if (replacement == p) {  // detach
                // pp-存储当前节点的父节点值
                HashMap.TreeNode pp = p.parent;
                // 当前节点的父节点指向NULL
                p.parent = null;
                // 如果父节点不为空,根据当前节点位于父节点的不同子节点,修改父节点的孩子节点值
                if (pp != null) {
                    if (p == pp.left)
                        pp.left = null;
                    else if (p == pp.right)
                        pp.right = null;
                }
            }
            // movable为true,需要将根节点移动到头节点,即数组所以位置指向的节点
            if (movable)
                moveRootToFront(tab, r);
        }

4、balanceDeletion(HashMap.TreeNode root, HashMap.TreeNode x)

        /**
         * 红黑树删除节点后,平衡红黑树的方法
         *
         * @param root 根节点
         * @param x    节点删除后,替代其位置的节点,这个节点可能是一个节点,也可能是一棵平衡的红黑树,在此处就当作一个节点,在该节点以上部分需要自平衡
         * @return 返回新的根节点
         * @Author muyi
         * @Date 11:33 2020/8/5
         */
        static  HashMap.TreeNode balanceDeletion(HashMap.TreeNode root, HashMap.TreeNode x) {
            /**
             * 进入这个方法,说明被替代的节点之前是黑色的,如果是红色的不会影响黑色高度,黑色的会影响以其作为根节点子树的黑色高度
             * xp-父节点,xpl-父节点的左孩子,xpr-父节点的右孩子节点
             * 注意:
             *    进入该方法的时候 替代节点可能与删除节点相等:x == replacement == p
             *                  替代节点可能与删除节点不相等:x == replacement != p
             */
            for (HashMap.TreeNode xp, xpl, xpr; ; ) {
                /**
                 * 1、x == null,当 replacement == p 时,删除节点不存在,返回;
                 *      因为当 replacement != p 时,replacement 肯定不会为null.在移除节点的方法中有三个地方对 replacement 进行赋值。
                 *          1、if (sr != null) replacement = sr;
                 *          2、if (pl != null) replacement = pl;
                 *          3、if (pr != null) replacement = pr;
                 * 2、x == root,如果替代完成后,该节点就是整棵红黑树的根节点,本身就是平衡的,直接返回
                 */
                if (x == null || x == root)
                    return root;
                else if ((xp = x.parent) == null) {
                    // 如果父节点为空,说明当前节点就是根节点,设置根节点的颜色为黑色,返回
                    x.red = false;
                    return x;
                } else if (x.red) {
                    /**
                     * 被替换节点(删除节点)的颜色是黑色的,删除之后黑色高度减1,如果替换节点是红色,将其设置为黑色,可以保证
                     * 1、与替换之前的黑色高度相等
                     * 2、满足红黑树的所有特性
                     * 达到平衡返回
                     */
                    x.red = false;
                    return root;
                    /**
                     * 如果替换节点是黑色的,替换之前的节点也是黑色的,替换之后,以替换节点作为根节点子树黑色高度减少1,需要进行相关的自平衡操作
                     * 1、替换节点是父节点的左孩子
                     */
                } else if ((xpl = xp.left) == x) {
                    /**
                     * 情况1、父节点的右孩子(兄弟节点)存在且为红色
                     * 处理方式:兄弟节点变黑,父节点变红,以父节点为支点进行左旋,重新获取兄弟节点,继续参与自平衡
                     */
                    if ((xpr = xp.right) != null && xpr.red) {
                        xpr.red = false;
                        xp.red = true;
                        root = rotateLeft(root, xp);
                        xpr = (xp = x.parent) == null ? null : xp.right;
                    }

                    // 不存在兄弟节点,x指向父节点,向上调整
                    if (xpr == null)
                        x = xp;
                    else {
                        // sl-兄弟节点的左孩子,sr-兄弟节点的右孩子
                        HashMap.TreeNode sl = xpr.left, sr = xpr.right;
                        /**
                         * 情况2-1:兄弟节点存在,且两个孩子的颜色均为黑色
                         * 1、sr == null || !sr.red:兄弟的右孩子为黑色(空节点的颜色其实也是黑色)
                         * 2、sl == null || !sl.red:兄弟的左孩子为黑色(空节点的颜色其实也是黑色)
                         * 处理方式:兄弟节点为红色,替换节点指向父节点,继续参与自平衡
                         */
                        if ((sr == null || !sr.red) && (sl == null || !sl.red)) {
                            xpr.red = true;
                            x = xp;
                        } else {
                            /**
                             * 该条件综合评价为:兄弟节点的右孩子为黑色
                             * 1、sr == null:兄弟的右孩子为黑色(空节点的颜色其实也是黑色)
                             * 2、!sr.red:兄弟节点的右孩子颜色为黑色
                             */
                            if (sr == null || !sr.red) {
                                /**
                                 * sl != null:兄弟的左孩子是存在且颜色是红色的
                                 * 情况2-2、兄弟节点右孩子为黑色、左孩子为红色
                                 * 处理方式:兄弟节点的左孩子设为黑色,兄弟节点设为红色,以兄弟节点为支点进行右旋,重新设置x的兄弟节点,继续参与自平衡
                                 */
                                if (sl != null)
                                    sl.red = false;
                                xpr.red = true;
                                root = rotateRight(root, xpr);
                                xpr = (xp = x.parent) == null ? null : xp.right;
                            }
                            /**
                             * 情况2-3、兄弟节点的右孩子是红色
                             * 处理方式:
                             * 1、如果兄弟节点存在,兄弟节点的颜色设置为父节点的颜色
                             * 2、兄弟节点的右孩子存在,颜色设为黑色
                             * 3、如果父节点存在,将父节点的颜色设为黑色
                             * 4、以父节点为支点进行左旋
                             */
                            if (xpr != null) {
                                xpr.red = (xp == null) ? false : xp.red;
                                if ((sr = xpr.right) != null)
                                    sr.red = false;
                            }
                            // 父节点不为空
                            if (xp != null) {
                                xp.red = false;
                                root = rotateLeft(root, xp);
                            }
                            // 替换节点指向根节点,平衡完成
                            x = root;
                        }
                    }
                } else {
                    /**
                     * 替换节点是父节点的右孩子节点
                     * 情况1、兄弟节点存在且为红色
                     * 处理方式:兄弟节点变黑,父节点变红,以父节点为支点进行左旋,重新获取兄弟节点,继续参与自平衡
                     */
                    if (xpl != null && xpl.red) {
                        xpl.red = false;
                        xp.red = true;
                        root = rotateRight(root, xp);
                        xpl = (xp = x.parent) == null ? null : xp.left;
                    }
                    // 不存在兄弟节点,x指向父节点,向上调整
                    if (xpl == null)
                        x = xp;
                    else {
                        // sl-兄弟节点的左孩子,sr-兄弟节点的右孩子
                        HashMap.TreeNode sl = xpl.left, sr = xpl.right;
                        /**
                         * 情况2-1:兄弟节点存在,且两个孩子的颜色均为黑色
                         * 1、sr == null || !sr.red:兄弟的右孩子为黑色(空节点的颜色其实也是黑色)
                         * 2、sl == null || !sl.red:兄弟的左孩子为黑色(空节点的颜色其实也是黑色)
                         * 处理方式:兄弟节点为红色,替换节点指向父节点,继续参与自平衡
                         */
                        if ((sl == null || !sl.red) && (sr == null || !sr.red)) {
                            xpl.red = true;
                            x = xp;
                        } else {
                            /**
                             * 该条件综合评价为:兄弟节点的左孩子为黑色
                             * 1、sr == null:兄弟的左孩子为黑色(空节点的颜色其实也是黑色)
                             * 2、!sr.red:兄弟节点的左孩子颜色为黑色
                             */
                            if (sl == null || !sl.red) {
                                /**
                                 * sl != null:兄弟的右孩子是存在且颜色是红色的
                                 * 情况2-2、兄弟节点左孩子为黑色、右孩子为红色
                                 * 处理方式:兄弟节点的右孩子设为黑色,兄弟节点设为红色,以兄弟节点为支点进行左,重新设置x的兄弟节点,继续参与自平衡
                                 */
                                if (sr != null)
                                    sr.red = false;
                                xpl.red = true;
                                root = rotateLeft(root, xpl);
                                xpl = (xp = x.parent) == null ? null : xp.left;
                            }
                            /**
                             * 情况2-3、兄弟节点的左孩子是红色
                             * 处理方式:
                             * 1、如果兄弟节点存在,兄弟节点的颜色设置为父节点的颜色
                             * 2、兄弟节点的左孩子存在,颜色设为黑色
                             * 3、如果父节点存在,将父节点的颜色设为黑色
                             * 4、以父节点为支点进行右旋
                             */
                            if (xpl != null) {
                                xpl.red = (xp == null) ? false : xp.red;
                                if ((sl = xpl.left) != null)
                                    sl.red = false;
                            }
                            if (xp != null) {
                                xp.red = false;
                                root = rotateRight(root, xp);
                            }
                            // 替换节点指向根节点,平衡完成
                            x = root;
                        }
                    }
                }
            }
        }

 

你可能感兴趣的:(HashMap源码分析)