HashMap源码解读

字段

//内部数组初始默认容量16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

//内部数组最大容量2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;

//默认的负载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

// 链表节点转换红黑树节点的阈值, 9个节点转
static final int TREEIFY_THRESHOLD = 8; 
 
// 红黑树节点转换链表节点的阈值, 6个节点转
static final int UNTREEIFY_THRESHOLD = 6;   
 
// 转红黑树时, table的最小长度
static final int MIN_TREEIFY_CAPACITY = 64; 
 
// 链表节点
static class Node<K,V> implements Map.Entry<K,V> {  
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
 
    // ... ...
}

// 内部数组,存储节点
transient Node<K,V>[] table;

//map被修改的次数
transient int modCount;

//内部数组扩容的阀值,容量 * loadFactor
int threshold;

//内部数组扩容的负载因子,默认0.75f
final float loadFactor;

重要方法

tableSizeFor

此方法传入一个数,先减1(后面会加回来),比如18,二进制表示00000000 00000000 00000000 00010010。再利用或运算的特性,只要有一个是1,那么运算结果就是1。因为一个是的最大位数总是1,再通过不断地利用错位或运算就可以保证最大位数的后面都是1,最后结果得到00000000 00000000 00000000 00011111. 最后n = 31,在执行n+1得到32.即此方法返回大于等于当前数的最小2的N次幂。

   /**
     * 取得大于等于cap,最小的2的N次方数,如:cap是6,则返回8,cap是16,则返回16
     * 以18举例,其二进制:00000000 00000000 00000000 00010010
     * 执行n |= n >>> 1
     * n >>> 1 后00000000 00000000 00000000 00001001
     * 00000000 00000000 00000000 00001001 上下或运算后,只要有1位是1,那么就是1
     * 00000000 00000000 00000000 00010010
     * 00000000 00000000 00000000 00011011 这样保证从左到右数有2位是1,下次可以移动2位
     * n >>> 2 后00000000 00000000 00000000 00000110
     * 00000000 00000000 00000000 00000110
     * 00000000 00000000 00000000 00011011
     * 00000000 00000000 00000000 00011111 这样保证从左到右数有4位是1,下次可以移动4位
     * 移动1位可以保证2位是1,移动2位可保证4位是1,移动4位可保证8位是1,移动8位可保证16位是1
     * 移动16位可保证32是1
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

resize

元素数组扩容:如果已经超过最大容量,则不再扩容,否则容量扩大一倍。扩容完毕后,Node重新放置到新元素数组上的索引位置。

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //如果已经扩容过,那么oldCap > 0
        if (oldCap > 0) {
        	//如果oldCap>=最大容量,那么不再扩容了
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 容量扩容一倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                //如果新容量未大于最大容量,那么将扩容阀值也扩大1倍,
                newThr = oldThr << 1; // double threshold
        } //如果元素数组为空(说明oldCap == 0可推断出元素数组位空),则说明为第一次扩容,且oldThrw为构造函数传入的初始化容量(如HashMap(int initialCapacity)
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else { 
        	//如果元素数组为空,则说明为第一次扩容,且使用无参构造函数
        	//则新容量为默认容量16,扩容阀值为16 * 0.75 = 12   
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
        	//如果新扩容阀值为空,则新扩容阀=新容量 * 负载因子
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        //非第一次扩容,则需要将原元素数组重新放入新的元素数组
        if (oldTab != null) {
        	//遍历元素数组,逐个将元素放到新的数组中
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                //先将元素取出赋值给e, 不为空才需要处理,如果为空那还需要移动个啥?
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                    	// e.next == null未形成链表或树,只有一个元素	
                    	//如果元素数组j位置上只有一个Node,
                    	//直接拿hash算出Node在新元素数组上的索引位置,然后安放元素e 
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                    	//如果是红黑数Node,单独分析
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                    	// 不是单元素或树,那则说明当前j位置是链表
                    	//如果元素数组j位置上为单链表。重新移动元素只会发生2种情况:
                    	// 一种是不需要变更索引位置的,
                    	// 另一种是需要变更索引位置的,但变更的位置都相同
                    	//loHead loTail 不需要变更索引位置的元素
                        Node<K,V> loHead = null, loTail = null;
                        //hiHead hiTail 需要变更索引位置的元素
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                        	//开始循环取元素去安放到新数组中
                            next = e.next;
                            //e.hash & oldCap == 0 这一类Node不需要变更索引位置
                            //假设:本次扩容由8 扩容至 16,这个链表的hash:1,9,17,25 
                            //1%8 == 1%16,17%8=17%16是不需要变更索引位置的
                            //9%8+原容量 = 9%16,25%8+原容量=25%16,这类元素只需要将原索引位置 + 原容量=新索引位置
                            //说白了,就是拿hash % 16,小于8的不需要变更索引位置,大于8的原索引位置 + 原容量
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        //遍历完所有的元素后, 分别将2个新的链表放到新的元素数组对应的位置上
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

hash

对key进行二次hash

 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

在key的hashCode基础上,再一次hash。但为什么要这么做呢?答案是减少key的碰撞,尽量使他们分布均匀点。

  1. 计算当前key在元素数组的索引时,使用的方法是hashCode & (n - 1) 这里的n是元素数组的长度

  2. 通常情况下,元素数组的长度不会太大,它的二进制高16位都位0.
    0000 0000 0000 0000 0000 0000 1111 1111 (n-1)的二进制表示
    0000 1101 0001 1111 0000 1101 1101 1000 一个hashCode
    0000 0000 0000 0000 0000 0000 1101 1000 &运算后得到的索引位置

    0000 0000 0000 0000 0000 0000 1111 1111 (n-1)的二进制表示
    0000 1101 0001 1001 0000 1101 1101 1000 另一个hashCode,与前一个hashCode不同仅仅是高16位不同,而低16位完全相同
    0000 0000 0000 0000 0000 0000 1101 1000 &运算后得到的索引位置
    由此可见,两个hashCode&运算后 得到的索引位置完全相同,因此而引人再次hashCode的算法。
    先无符号向右移动16位,然后和原数进行异或运算,使得新得到的数带上了高16与低16位的特征,再和(n-1)与运算,就不在碰撞

    第一组hashCode
    0000 1101 0001 1111 0000 1101 1101 1000
    0000 0000 0000 0000 0000 1101 0001 1111 异或运算相同为0,不同为1
    0000 1101 0001 1111 0000 0000 1110 0111
    第二组hashCode
    0000 1101 0001 1111 0000 1101 1101 1000
    0000 0000 0000 0000 0000 1101 0001 1001 异或运算相同为0,不同为1
    0000 1101 0001 1111 0000 0000 1100 0001
    可见它们的低16不再相同,和(n-1)的&运算后不再相同

Get方法

Get

get(Object key) 调用的还是getNode方法,所以只看getNode的源码.

final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //先根据hash&(n-1)找到元素数组的索引位置,并取出该位置上的Node元素
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            //如果该元素Node的hash一致,并且Node的value和传入的key对象比较相等,则认为已经找到,直接返回该Node
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
             //如果该索引位置不止一个元素
            if ((e = first.next) != null) {
                if (first instanceof TreeNode) //如果是红黑树,则通过红黑树寻找
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                //否则通过单链表寻找
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

Put方法

put(K key, V Value)

注意的是HashMap是(n-1)&hash来定位元素数组的index。当n等于2的N次幂时,(n-1)&hash = hash % n,所以HashMap的容量一定是2的N次幂,即便传入的初始容量不是2的N次幂,它也会把容量变成大于传入初始容量的最小的2的N次幂。为什么要用(n-1)&hash代替hash % n?原因无它,位运算的性能更高。JDK的代码追求极致的性能

	public V put(K key, V value) {
		//第一个参数:key二次哈希,第四个参数:值覆盖
        return putVal(hash(key), key, value, false, true);
    }
	/**
	* onlyIfAbsent: true 如果V已存在且不为null,则不更新
	*/		
	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)
        	//如果元素数组长度为0或为null,则进行第一次扩容
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
        	//在元素数组(n-1)&hash位置上的索引位置上 如果为null,说明这里未放置过Node,那么将K,V封装成Node
        	//放入这个索引位置上
            tab[i] = newNode(hash, key, value, null);
        else {
        	//否则说明这个索引位置上已经放置了Node,先判断该位置上的Node(即下面的变量p)的key是否和参数key是否相等,
        	//如果相等则说明找到本次Put的位置。
            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)
            	//如果p是TreeNode,依据红黑树的规则进行添加Node
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            	//否则就遍历单链表,再决定是要插入还是要更新
                for (int binCount = 0; ; ++binCount) {
                	//检查p指向的next是否未null,如果为null,说明遍历完整个链表都未发现put过此key
                	//直接新增一个node指向p的next,即添加到链表的尾部,并break当前for循环
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //如果链表长度大于等于8,则尝试单链表转化为红黑树
                        //注意binCount是从0开始的,所以判断是>= TREEIFY_THRESHOLD - 1
                        //treeifyBin方法内部也会判断元素数组tab的长度是否达到64
                        //如果没达到则扩容不转红黑树,否则转红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //走到这里说明p.next不为空,则判断下p.next指向的Node的key是否与传入的
                    //参数k相等, 如果相等则说明当前元素e是要找的更新的Node, break中断for循环
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //如果走到这里说明元素e还不是要找的Node,但链表还未检查完,则将p指向e节点,
                    //即p指向p.next, 继续通过for循环来执行链表的检查
                    p = e;
                }
            }
            //如果找到某个Node的Key和传入的参数Key相等
            //如果onlyIfAbsent == true,则只有Node的value是null才会更新
            //onlyIfAbsent == false,则不关心Node的值是否未null,直接覆盖更新
            // 执行完更新操作,则return当前Node的旧值
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        // 更新次数+1
        ++modCount;
        //走到这里说明是新增Node, 因为在上面代码里执行更新后就return了。
        //那么先将size加1,再看size是否大于扩容阀值后再扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict); //用于子类在put完之后的扩展操作
        return null;
    }

remove方法

remove

remove(Object key) 是通过调用removeNode方法实现的,所以只看removeNode的源码

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;
        //判断下元素数组table是否未空,不为空则开始开始进一步的判断
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            //先通过(n - 1) & hash算出当前key所在元素数组的索引index,取出对应的Node指向p
            Node<K,V> node = null, e; K k; V v;
            //如果p的hash和key的hash相等,同时equals判断也相等,说明p就是要找的Node
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            else if ((e = p.next) != null) {
                //如果p不是要找的Node, 则判断p.next指向的Node是否为null
                //如果不为null则判断是否为红黑树,如果是则基于红黑树判断
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                	//如果不是红黑树, 那就是循环遍历单链表指导找到node为止
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            //如果找到对应的node. 判断是否要matchValue
            // 如果matchValue == false直接开始移除动作
            // 如果matchValue == true 还需要判断传入的value是否和当前Node的value是否相等,相等才开始移除动作
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                 //如果是红黑树,则通过红黑树移除
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                	//如果node == p,说明p指向的是头节点,要移除的Node是链表的头节点
                	//只需要把头节点的下一个节点顶替p放入到元素数组即可
                    tab[index] = node.next;
                else
                	//到这里则说明p已经经过遍历链表不是链表的头节点,而是中间节点
                	//这个时候p.next ==> node
                	//只需p.next ==> node.next, 把node从链表中取出
                    p.next = node.next;
                //hashMap的更新次数+1    
                ++modCount;
                //hashMap的Node的总个数 - 1,即hashMap的已存储的元素-1
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        //走到这里说明元素数组为空或未找到对应的Node或不满足matchValue而未执行移除
        return null;
    }

红黑树相关方法

treeify (Node[] tab) 链表转为红黑树

TreeNode的方法,将链表转化为红黑树。红黑树的具体论述可以自行查阅,这里只讲下红黑树5个基本特征:

  1. 所有节点不是红色节点就是黑色节点
  2. 根节点是黑色节点
  3. 所有的叶子节点都是黑色节点。(叶子节点指NIL结点)
  4. 红色节点的父节点都是黑色节点,也可以推导出红色节点的子节点都是黑色节点
  5. 任意节点到各叶子节点的路径上的黑色节点个数都数相等的
final void treeify(Node<K,V>[] tab) {
    TreeNode<K,V> root = null;
    //this指当前TreeNode节点,指向x变量,它同时也是一个双链表
    //从this开始遍历双链表, 转化为红黑树 
    for (TreeNode<K,V> x = this, next; x != null; x = next) {
    	//将x的next节点指向变量next,便于下次遍历
        next = (TreeNode<K,V>)x.next;
        x.left = x.right = null;
        //如果当前红黑树无根节点,则将x指向根节点
        if (root == null) {
            x.parent = null;
            x.red = false;
            root = x;
        }
        else {
            K k = x.key;
            int h = x.hash;
            Class<?> kc = null;
            //如果根节点已存在,那么从根节点开始遍历红黑树,查到到x节点应该插入的位置
            for (TreeNode<K,V> p = root;;) {
                int dir, ph;
                K pk = p.key;
                //dir为要插入的方向,小于等于0为向左节点插入,大于0为向右节点插入
                //先比较x和p的hash值,x小就dir=-1,否则就是1
                //如果x和p的hash值相等,则判断x的类是否实现了Comparable接口,如果实现了
                //则调用x的Comparable接口的compareTo来判断谁大谁小
                //最后兜底判断方法tieBreakOrder:判断System.identityHashCode的大小
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0)
                    dir = tieBreakOrder(k, pk);

                TreeNode<K,V> xp = p;
                //xp指向p指向的节点,如果是要向左插入,则p指向p的左节点,否则指向右节点
                //同时判断p最新指向的节点是否为空,不为空则说明这里已经有节点,需要继续
                //for判断x能否插入p节点的左右节点。如果为空,则说明这个坑其他节点还未占着
                //可以插入
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                	//此时x节点的父节点指向原p指向的节点,并判断x是挂在左节点还是右节点上
                    x.parent = xp;
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    //插入节点后需要处理红黑树的自平衡    
                    root = balanceInsertion(root, x);
                    break;
                }
            }
        }
    }
    moveRootToFront(tab, root);
}

balanceInsertion 插入自平衡

红黑树自平衡方法,因为插入一个新的节点,都是红色节点。为了保证红黑树的特性,需要对红黑树的部分节点重新着色,左旋或右旋。
自平衡总结:

  1. 插入的节点(称当前节点,下文均以此称呼)着红色,因为红色不会破坏[任意节点到各叶子节点的路径上的黑色节点个数都相等]这条规则,可以减少自平衡操作
  2. 如果根节点为空,则当前节点当作根节点,自平衡结束
  3. 如果当前节点的父节点是黑色节点, 自平衡结束。因为不会破坏红黑树的任何特性
  4. 如果当前节点的父节点是红色节点,则需要分2种情况处理:
    4-1. 当前节点是父节点的左孩子:
    4-1-a:
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
	 //x代表新插入的节点,给予赋红色。因为红色不会破坏特性:
	 //任意节点到各叶子节点的路径上的黑色节点个数都相等。可以减少自平衡的操作
	 x.red = true;
	 //从x节点开始处理
     for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
     	 //x.parent为null,说明它是根节点,不需要任何操作
         if ((xp = x.parent) == null) {
             x.red = false;
             return x;
         }
         //xp,即x的父节点是黑色节点或是根节点,不需要任何操作
         //因为不会破坏红黑树的5个特性
         else if (!xp.red || (xpp = xp.parent) == null)
             return root;
         //如果父节点是红色节点,且父节点是祖节点的左孩子    
         if (xp == (xppl = xpp.left)) {
              //如果叔叔节点也是红色节点。则把父节点和叔叔节点标记为黑色。
              //祖节点标记为红色,且x节点指向祖节点,重新for循环判断红黑树的自平衡
             if ((xppr = xpp.right) != null && xppr.red) {
                 xppr.red = false;
                 xp.red = false;
                 xpp.red = true;
                 x = xpp;
             }
             else {
                 //走到这里则说明叔叔节点不存在或为黑色节点
                 //如果x是父节点的右节点
                 if (x == xp.right) {
                 	//则x指向父节点,进行一次左旋操作。左旋后原x节点变成父节点,
                 	//原父节点变成原x节点的左节点
                     root = rotateLeft(root, x = xp);
                     //让xp和xpp重新指向最新的节点
                     xpp = (xp = x.parent) == null ? null : xp.parent;
                 }
                 //走到这里说明x是父节点的左孩子,且x节点和父节点是红色节点,祖节点是黑色节点
                 //将父节点着色成黑色节点,祖节点着色成红色。并对祖节点进行一次右旋
                 //因父节点已是黑色节点,再次指向for循环时则会退出循环,说明自平衡结束 
                 if (xp != null) {
                     xp.red = false;
                     if (xpp != null) {
                         xpp.red = true;
                         root = rotateRight(root, xpp);
                     }
                 }
             }
         }
         else {
         	 //走到这里,说明x和x的父节点都是红色节点,且父节点是祖节点的右孩子
         	 //如果叔叔节点也是红色节点,则将父节点和叔叔节点着色成黑色节点,
         	 //祖节点着色成红色节点。x节点指向祖节点,重新for循环判断红黑树的自平衡
             if (xppl != null && xppl.red) {
                 xppl.red = false;
                 xp.red = false;
                 xpp.red = true;
                 x = xpp;
             }
             else {
             	 //走到这里说明叔叔节点为空或为黑色节点,x和x的父节点都是红色节点
             	 //如果x是父节点的左孩子,先进行一次右旋
                 if (x == xp.left) {
                 	 //x指向父节点,进行一次右旋,右旋后原x节点成为父节点
                 	 //原父节点成为当前父节点的右孩子, x指向这个右孩子
                     root = rotateRight(root, x = xp);
                     //重新让父节点和祖节点变量指向最新的TreeNode
                     xpp = (xp = x.parent) == null ? null : xp.parent;
                 }
                 //走到这里说明x是父节点的右孩子,2种情况:
                 //1.本身x就是父节点的右孩子 
                 //2.即上述情况,x是父节点的左孩子,但经过一次右旋,x重新指向父节点的右孩子
                 //此时x和父节点都是红色,祖节点是黑色。把父节点着色成黑色,祖节点着色成红色
                 //然后对祖节点进行一次左旋。
                 //此时x节点的父节点成了黑色。下次for循环的时候将退出for循环,自平衡结束
                 if (xp != null) {
                     xp.red = false;
                     if (xpp != null) {
                         xpp.red = true;
                         root = rotateLeft(root, xpp);
                     }
                 }
             }
         }
     }
 }

removeTreeNode 删除节点

红黑树删除节点

final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                                  boolean movable) {
   int n;
    if (tab == null || (n = tab.length) == 0)
        return;
    int index = (n - 1) & hash;
    //先从HashMap的元素数组取出红黑树, 第一个节点肯定是根节点
    TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
    TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
    //TreeNode不但是颗红黑树,也是个双链表。先更新链表
    //如果前置节点不存在,说明当前待删除的Node是链表的头节点, 把
    //把后置节点变成头节点,放到HashMap的元素数组中
    if (pred == null)
        tab[index] = first = succ;
    else
    	//如果当前节点是中间节点,前置节点的next指向后置节点
        pred.next = succ;
    //把后置节点的prev指向前置节点,让当前节点从双链表中拿出来    
    if (succ != null)
        succ.prev = pred;
    //如果fist为空,说明链表已经为空了,那自然红黑树也不存在了,无需处理,直接return    
    if (first == null)
        return;
    if (root.parent != null)
        root = root.root();
     //如果root为空,或树只剩1或2或3个节点,没必要维持红黑树,转成链表形式
    if (root == null
        || (movable
            && (root.right == null
                || (rl = root.left) == null
                || rl.left == null))) {
        tab[index] = first.untreeify(map);  // too small
        return;
    }
    TreeNode<K,V> p = this, pl = left, pr = right, replacement;
    //如果p的左孩子和右孩子都不为空,那么向右孩子这边查找大于p的最小节点s
    //然后s和p互换位置和颜色,其实就是删除p后把s放到p的位置上,也能保持整个树的平衡
    //即值都大于左孩子,都小于右孩子
    //这样p放到s的位置上就演变成最多拥有一个右孩子的节点,更方便删除 
    if (pl != null && pr != null) {
        TreeNode<K,V> s = pr, sl;
        //从p的右孩子pr出发,寻找它的左孩子,如果左孩子还有左孩子
        //那么while循环一直找到没有左孩子为止的那个节点,然后指向s
        while ((sl = s.left) != null) // find successor
            s = sl;
        //p和s互换颜色
        boolean c = s.red; s.red = p.red; p.red = c; // swap colors
        TreeNode<K,V> sr = s.right;
        TreeNode<K,V> pp = p.parent;
        //p和s互换位置(如果s是p的右孩子)
        if (s == pr) { // p was s's direct parent
            p.parent = s;
            s.right = p;
        }
        else {
        	//p和s互换位置(s不是p的右孩子)
            TreeNode<K,V> sp = s.parent;
            if ((p.parent = sp) != null) {
                if (s == sp.left)
                    sp.left = p;
                else
                    sp.right = p;
            }
            if ((s.right = pr) != null)
                pr.parent = s;
        }
        p.left = null;
        if ((p.right = sr) != null)
            sr.parent = p;
        if ((s.left = pl) != null)
            pl.parent = s;
        if ((s.parent = pp) == null)
            root = s;
        else if (p == pp.left)
            pp.left = s;
        else
            pp.right = s;
        //如果与p互换位置的节点还有右节点,replacement(需要去自平衡的节点)就是sr
        //这种情况下p肯定是黑色节点,sr是红色节点
        //因为p经过上述的while判断是一定没有左孩子,那么sr就不可能是黑色
        //因为会破坏红黑树的结构(从p出发到左右孩子的叶子节点的路径中,黑色节点数须相等)
        //所以sr只能是只能是红色
        //又根据红色节点不能互相挨着,所以p只能是黑色
        //那么这种情况删除自平衡就简单了,sr直接变成黑色节点就结束了
        //p删除掉后少了一个黑色节点,sr变成黑色节点又加回来了   
        if (sr != null)
            replacement = sr;
        else
        	//否则replacement就是p,p可能是黑色也可能是红色
            replacement = p;
    }
   //如果p只有左孩子,那么replacement就是左孩子
   //这种情况像上方的类似,当一个节点p只有一个孩子节点的时候
   //p必定是黑色,孩子必定为红色
   //这种情况删除自平衡就简单了,只需把replacement由红色变成黑色即可
   //这辆删除掉p后少一个黑色节点,replacement变成黑色后又加回一个黑色节点
   //这样就可以保持树的平衡
    else if (pl != null)
        replacement = pl;
    else if (pr != null)
        replacement = pr;
    else
    	//如果p没有孩子,那么replacement就是p,可能是黑色,也可能是红色
        replacement = p;
    //如果replacement != p,就p拥有一个孩子的这种情况
    //把p从树中移除
    if (replacement != p) {
        TreeNode<K,V> pp = replacement.parent = p.parent;
        if (pp == null)
            root = replacement;
        else if (p == pp.left)
            pp.left = replacement;
        else
            pp.right = replacement;
        p.left = p.right = p.parent = null;
    }
	//如果待删除的p自己本身就是红色,即便删除它也不会破坏红黑树的特性,无需删除自平衡
	//否则就需要删除自平衡	
    TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
	//如果replacement是p,把p从树中移除
    if (replacement == p) {  // detach
        TreeNode<K,V> pp = p.parent;
        p.parent = null;
        if (pp != null) {
            if (p == pp.left)
                pp.left = null;
            else if (p == pp.right)
                pp.right = null;
        }
    }
    if (movable)
        moveRootToFront(tab, r);
}

balanceDeletion 删除自平衡

删除红色节点不会破坏红黑树的5个特性,所以只有删除黑色节点才需要自平衡。中心思想就是向父节点的另一个孩子借一个红色节点变成黑色,达到满足特性:从任意一节点出发,到其所有叶子节点的路径上黑色节点都是相等的

  static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
                                                   TreeNode<K,V> x) {
      //x为需要自平衡的节点, 从x节点开始
      for (TreeNode<K,V> xp, xpl, xpr;;) {
      		//如果x是根节点或null,不需要自平衡
            if (x == null || x == root)
                return root;
            //如果x的父节点为空,说明x当前是父节点    
            else if ((xp = x.parent) == null) {
                x.red = false;
                return x;
            }
            //如果x是红色节点,只需变成黑色即可,就不需要再向父节点的右孩子借一个红色节点
            else if (x.red) {
                x.red = false;
                return root;
            }
            //如果x是左孩子,且为黑色节点
            else if ((xpl = xp.left) == x) {
            	//如果xp的右孩子是红色节点.说明此时xp是黑色节点
            	//xpr的孩子是黑色节点
                if ((xpr = xp.right) != null && xpr.red) {
                	//右孩子变成黑色,父节点变成红色
                    xpr.red = false;
                    xp.red = true;
                    //对父节点左旋。让右孩子顶替原父节点的位置和颜色
                    //保证右孩子这边的黑色节点数保持不变, x(左孩子)这边多了一个红色节点
                    root = rotateLeft(root, xp);
                    //重新赋值下xp和xpr.
                    //xpr是原xpr的左孩子,经过xp的左旋后,成为了xp的右孩子
                    //此时xp是红色节点,x和xpr是黑色节点(是下文所说的第三种或第四种情况)
                    xpr = (xp = x.parent) == null ? null : xp.right;
                }
                //如果右孩子为空,向右边已经借不到节点来平衡树。
                //我没发现会有这种情况,因为x是黑节点,xp的右孩子这边肯定至少有一个黑色节点
                if (xpr == null)
                    x = xp;
                else {
                    TreeNode<K,V> sl = xpr.left, sr = xpr.right;
                    //第一种情况:x是黑色,xp是黑色,xpr黑色。xpr无孩子
                    //第二种情况:x是黑色,xp是黑色,xpr黑色。xpr有红孩子
                    //第三种情况:x是黑色,xp是红色,xpr黑色。xpr无孩子
                    //第四种情况:x是黑色,xp是红色,xpr黑色。xpr有红孩子
                    //xpr必定为黑色。因为即便是xpr是红色,经过上述的xpr = xp.right) != null && xpr.red判断
                    //xpr也会变成黑色节点
                    
                    //下列if判断即:如果右孩子的左右节点都没有红色节点(没法借一个红色节点,就交给父节点去借)
                    //针对一、三种情况,已经没法向右孩子来借一个节点保持平衡,只能让右孩子
                    //变成红色,保持左右各少一个黑色节点,让父节点再向他的兄弟节点借一个节点
                    //来保持红黑树平衡。
                    if ((sr == null || !sr.red) &&
                        (sl == null || !sl.red)) {
                        //只能让右孩子xpr也变成红色,保证x和xpr两边都少一个黑色节点
                        //让父节点去自平衡争取回来一个黑色节点来保证左右两边的黑色节点数
                        //是不会少的
                        xpr.red = true;
                        //xp赋值给x,去判断自平衡
                        //第三种情况:xp是红色节点,再次for循环时在判断是否红色节点时,即可变成黑色退出自平衡跳出for循环
                        //第一种情况:没办法,只能让xp变成x走for循环去和它的兄弟节点借一个红色节点过来
                        x = xp;
                    }
                    //二、四种情况不满足上诉条件,则走else判断
                    else {
                    	//二、四种情况:
                    	//第二种情况:x是黑色,xp是黑色,xpr黑色。xpr有红孩子
                    	//第四种情况:x是黑色,xp是红色,xpr黑色。xpr有红孩子
                    	//走到这里说明右孩子的左右节点至少有一个是红色节点
                    	//如果右孩子的右节点不是红色节点,则左节点必定是红色节点
                        if (sr == null || !sr.red) {
                        	//那么右孩子的左节点不为空且必定是红色节点,此时把它变成黑色节点
                            if (sl != null)
                                sl.red = false;
                            //右孩子由黑色变成红色     
                            xpr.red = true;
                            //再对xpr(右孩子)进行右旋,此时xpr的左孩子是空的
                            root = rotateRight(root, xpr);
                            //重新对右孩子赋值,此时右孩子是原右孩子的左孩子(黑色)
                            //原右孩子变成现在的右孩子的右节点(红色)
                            xpr = (xp = x.parent) == null ?
                                null : xp.right;
                        }
                        //此时
                        //第二种情况:x是黑色,xp是黑色,xpr黑色。xpr右孩子是红色,xpr左孩子是红色(可能红色节点下面挂着黑色节点)或NIL
                    	//第四种情况:x是黑色,xp是红色,xpr黑色。xpr右孩子是红色,xpr左孩子是红色节点(可能红色节点下面挂着黑色节点)或NIL
                        if (xpr != null) {
                        	//xpr(右孩子)变成父节点的颜色, 红色或黑色,下文中xp左旋后,顶替父亲的角色  
                            xpr.red = (xp == null) ? false : xp.red;
                            //右孩子的右节点红色变成黑色,保证右孩子这边不会少一个红色节点  
                            if ((sr = xpr.right) != null)
                                sr.red = false;
                        }
                         //此时:xpr右孩子右红色变成黑色, 保证右边即便被借走一个也保证黑色节点个数不变
                         //xpr通过左旋被借走,顶替成xp节点,变成xp的颜色,xp变成黑色
                        //第二种情况:x是黑色,xp是黑色,xpr和xp颜色一致。xpr右孩子是黑色
                    	//第四种情况:x是黑色,xp是红色,xpr和xp颜色一致。xpr右孩子是黑色
                        if (xp != null) {
                        	//把父节点变成黑色 
                            xp.red = false;
                            //再对父节点左旋, 向右边借一个节点成功,保证左边多了一个黑色节点
                            //父节点左旋后,xpr的左孩子会变成xp的右孩子。
                            //上文中说到xpr的左孩子可能的情况是红色节点或NIL,xp变成黑色,x删除后不会破坏树平衡
                            //还有一种情况是红色节点带着黑色节点,这种情况是上述的第一种情况xp赋值给x,由xp来自平衡造成的
                            //但此时它的黑色节点数是和x这边的黑色节点数是相同的,也不会破坏树的平衡
                            //自此向右借一个节点结束
                            root = rotateLeft(root, xp);
                        }
                        //把root赋值给x跳出for循环
                        x = root;
                    }
                }
            }
            //x是xp的右孩子,和上面分析的类似,上面看明白了,下面自然就明白
            else { // symmetric
                if (xpl != null && xpl.red) {
                    xpl.red = false;
                    xp.red = true;
                    root = rotateRight(root, xp);
                    xpl = (xp = x.parent) == null ? null : xp.left;
                }
                if (xpl == null)
                    x = xp;
                else {
                    TreeNode<K,V> sl = xpl.left, sr = xpl.right;
                    if ((sl == null || !sl.red) &&
                        (sr == null || !sr.red)) {
                        xpl.red = true;
                        x = xp;
                    }
                    else {
                        if (sl == null || !sl.red) {
                            if (sr != null)
                                sr.red = false;
                            xpl.red = true;
                            root = rotateLeft(root, xpl);
                            xpl = (xp = x.parent) == null ?
                                null : xp.left;
                        }
                        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;
                    }
                }
            }
        }
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值