HashMap源码深度分析(JDK1.8)

 

目录

1. 网上关于HashMap的一些问题的错误解析

2. 关于HashMap中一些设计

(1) hash值的计算

(2) 索引的计算

(3)hash表容量必须为2的等次幂

(4) 在链表长度超过8时, 链表可能会转为红黑树

(5) 最小红黑树容量: MIN_TREEIFY_CAPACITY

(6) String类型变量的hash值生成策略

3. HashMap相关定义

成员变量

基本属性默认值

链表节点

红黑树节点

4. HashMap节点的添加: put

相关方法1: putTreeVal

相关方法2: treeifyBin

相关方法3: treeify

相关方法4: moveRootToFront

 相关方法5:tableSizeFor

相关方法6: putMapEntries

5. HashMap节点的获取: get

相关方法1:getTreeNode

相关方法2:find

6. HashMap的扩容:resize

相关方法1:split

7.HashMap中节点的删除:remove

相关方法1: removeTreeNode

8.HashMap中红黑树相关方法

左旋转: rotateLeft

右旋转: rotateRight

插入后调整红黑树平衡:balanceInsertion

删除后调整红黑树平衡:balanceDeletion


1. 网上关于HashMap的一些问题的错误解析

为什么负载因子默认为 0.75?

关于负载因子的设定, 网上大部分都是粘贴出来源码中的注释:

Because TreeNodes are about twice the size of regular nodes, we
use them only when bins contain enough nodes to warrant use
(see TREEIFY_THRESHOLD). And when they become too small (due to
removal or resizing) they are converted back to plain bins.  In
usages with well-distributed user hashCodes, tree bins are
rarely used.  Ideally, under random hashCodes, the frequency of
nodes in bins follows a Poisson distribution
(http://en.wikipedia.org/wiki/Poisson_distribution) with a
parameter of about 0.5 on average for the default resizing
threshold of 0.75, although with a large variance because of
resizing granularity. Ignoring variance, the expected
occurrences of list size k are (exp(-0.5)  pow(0.5, k) /
factorial(k)). The first values are:

0:    0.60653066
1:    0.30326533
2:    0.07581633
3:    0.01263606
4:    0.00157952
5:    0.00015795
6:    0.00001316
7:    0.00000094
8:    0.00000006
more: less than 1 in ten million

大概翻译如下:

因为TreeNode的大小约为链表节点的两倍,所以仅在一个链表中包含足够多的节点时才会转为红黑树(参考TREEIFY_THRESHOLD)。 当它们变得太小时(由于移除或调整大小), 我们会将红黑树转为链表, 如果一个用户的数据他的hashcode值分布十分好的情况下,将会很少使用到红黑树结构。在理想情况下,使用随机hashCode值下, 负载因子为0.75,尽管由于粒度调整会产生较大的方差, key经过hash计算后进入某一个桶的概率为0.5, 即桶中的Node的分布频率服从参数为0.5的泊松分布, 预期列表大小k的出现是(exp(-0.5) * pow(0.5, k) /  * factorial(k))

因为这个是在jdk1.8中HashMap上的注释, 并且也提到了负载因子为0.75, 很多人并没有好好理解这段话的意思, 就强行将泊松分布于负载因子关联在一起。但是这段话的意思在于说明为什么TREEIFY_THRESHOLD的值会设置为8, 也就是为什么HashMap中链表长度超过8时才会转为红黑树, HashMap中TREEIFY_THRESHOLD的设定是为了尽可能的避免出现链表转红黑树的情况, 但是另一方面需要防止在恶意的数据规模中, 出现HashMap退化为链表的情况;  在注释中提到了负载因子, 这里的意思是, 注释中泊松分布的数据模型是基于负载因为为0.75的情况进行推演的; 

对于为什么会默认为0.75,  目前还没有找到最严谨的推算方式, 也有说可以利用Hash碰撞通过二项式进行推算, 但是Hash碰撞并不完全符合二项分布的条件...

2. 关于HashMap中一些设计

(1) hash值的计算

不论是链表节点还是红黑树节点, 其中都维护着该节点key的hash值, 因为在hashMap的各个方法中都频繁的应用到了hash值; 在红黑树中计算索引时增加了扰动计算, 也就是在计算key的hash值后, 将key的的高16位与低16位进行异或运算, 减少hash碰撞

(2) 索引的计算

在计算索引时并没有使用%进行取模运算, 而是使用了hash值hash表长度-1进行与运算来获取hash值对应的索引, 提高计算效率

hash:      1  0  1  0
                                &
tabCap-1:   1  1  1
                                 =
index:         0  1  0

hash表容量为1000, 那么在hash值中低于第四位的值都是hashCap的模

(3)hash表容量必须为2的等次幂

根据上面索引的计算可以得出, 之后在hash表长度为2的等次幂, 在转为二进制数值后, 除了最高位其他位的数值均是0, 在tabCap-1, 所有位置的数值均为1, 与hash值进行与运算获取到模, 即索引

(4) 在链表长度超过8时, 链表可能会转为红黑树

HashMap每个桶中可能存在值的几率是0.5, 即λ = 0.5
泊松分布公式: exp(-0.5) * pow(0.5, k) / factorial(k)
多个节点经过hash计算索引后, 在出现hash冲突, 在同一个桶内的概率符合泊松分布
(注意:下面的数据模型是基于负载因子为0.75):

0:    0.60653066
1:    0.30326533
2:    0.07581633
3:    0.01263606
4:    0.00157952
5:    0.00015795
6:    0.00001316
7:    0.00000094
8:    0.00000006
more: less than 1 in ten million

当8个节点出现hash冲突在同一个桶内的概率为0.00000006, 所以在链表长度超过8的时候【可能】转为红黑树,
可见Hash表这样设计的本意是为了避免出现链表转为红黑树的情况

TREEIFY_THRESHOLD的设定是为了避免在出现链表转红黑树的情况, 但是另一方面需要防止在特定的数
据规模中,出现HashMap退化为链表导致查询效率急剧下降的情况

(5) 最小红黑树容量: MIN_TREEIFY_CAPACITY

 HashMap中链表长度超过8的时候【可能】转为红黑树; 
 当链表长度超过8时, 首先判断hash表的长度是否大于最小红黑树容量(也就是hash表中出现红黑树需要达到的最小容量); 
 大于等于最小红黑树容量才会将链表转换为红黑树, 【如果小于, 则进行的是扩容操作, 而不是转换红黑树】

(6) String类型变量的hash值生成策略

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

示例:

字符串 java,由 j、a、v、a 四个字符组成
因此,java 的哈希值可以表示为 j ∗ n^3 + a ∗ n^2 + v ∗ n^1 + a ∗ n^0, 等价于 [ ( j ∗ n + a ) ∗ n + v ] ∗ n + a 

在JDK中,乘数 n 为 31,为什么使用 31?
(1) 31符合2^n – 1,JVM会将 31 * i 优化成 (i << 5) – i 
(2) 31是一个奇素数(既是奇数,又是素数,也就是质数), 素数和其他数相乘的结果比其他方式更容易产成唯一性,减少哈希冲突 

还有其他在后面的源码解析中体现, 会有相关注明


3. HashMap相关定义

成员变量

/**
 * 桶; 存储链表的头节点的表, 或红黑树的根节点的表
 */
transient Node<K,V>[] table;  

/**
 * 存储缓存的set集合
 */
transient Set<Map.Entry<K,V>> entrySet;  

/**
 * HashMap中实际存储数据的数量
 */
transient int size;  

/**
 * HashMap结构上被修改的次数
 */
transient int modCount;  

/**
 * 阈值, 当size超过该阈值, 则进行扩容; ( 容量 * 负载系数 ) 16 * 0.75f = 12,
 */
int threshold;  

/**
 * 负载因子 0.75
 * 负载因子的用处, 最大可能的避免出现Hash冲突
 */
final float loadFactor;

基本属性默认值

/**
 * 默认初始化容量 - 必须为2的等次幂 (编译后面利用左移右移进行计算)
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**
 * 最大容量, 必须小于2的30次方
 */
static final int MAXIMUM_CAPACITY = 1 << 30;

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

/**
 *
 * 当链表长度达到8时, 将链表转为红黑树;
 *  注意:  HashMap每个slot可能存在值的几率是0.5, 即λ = 0.5
 *        泊松分布公式: exp(-0.5) * pow(0.5, k) / factorial(k)
 *        多个节点经过hash计算索引后, 在出现hash冲突, 在同一个桶内的概率符合泊松分布
 *        (注意:下面的数据模型是基于负载因子为0.75):
 *
 *   0:    0.60653066
 *   1:    0.30326533
 *   2:    0.07581633
 *   3:    0.01263606
 *   4:    0.00157952
 *   5:    0.00015795
 *   6:    0.00001316
 *   7:    0.00000094
 *   8:    0.00000006
 *   more: less than 1 in ten million
 *
 *   当8个节点出现hash冲突在同一个桶内的概率为0.00000006, 所以在链表长度超过8的时候【可能】转为红黑树,
 *   可见Hash表这样设计的本意是为了避免出现链表转为红黑树的情况
 *
 *   TREEIFY_THRESHOLD的设定是为了避免在出现链表转红黑树的情况, 但是另一方面需要防止在特定的数
 *   据规模中,出现HashMap退化为链表导致查询效率急剧下降的情况
 *
 */
static final int TREEIFY_THRESHOLD = 8;

/**
 * 当红黑树中的节点数量小于6时,红黑树将转换为链表
 */
static final int UNTREEIFY_THRESHOLD = 6;

/**
 *
 *  HashMap中链表长度超过8的时候【可能】转为红黑树
 * 当链表长度超过8时, 首先判断hash表的长度是否大于最小红黑树容量(也就是hash表中出现红黑树需要达到的最小容量)
 *  大于等于最小红黑树容量才会将链表转换为红黑树, 【如果小于, 则进行的是扩容操作, 而不是转换红黑树】
 */
static final int MIN_TREEIFY_CAPACITY = 64;

链表节点

/**
 * Node节点,
 *    1.存储节点数据(key, value)
 *    2.存储key的hash值
 *    3.下一个节点的索引(用于链表模式下指向下一个节点, 单向链表)
 * 为什么使用单向链表?
 *    1. 节省内存, 仅存储下一个节点的地址即可
 *    2. 当出现hash值相同的key时, key会遍历该链表所有的节点, 判断是否存在相同的key,
 *    如果有, 则覆盖该节点. 如果查询至链表尾部, 仍然没有相同key,则就在该链表尾部新增节点
 */
static class Node<K,V> implements Map.Entry<K,V> {
	final int hash;  //存储key的hash值, 用于计算该key在Hash表中的索引位置
	final K key;     //key值, 不允许修改, 覆盖 (注:如果出现相同key,则直接覆盖value即可)
	V value;
	Node<K,V> next;  //下一个节点的地址

	Node(int hash, K key, V value, Node<K,V> next) {
		this.hash = hash;
		this.key = key;
		this.value = value;
		this.next = next;
	}

	public final K getKey()        { return key; }
	public final V getValue()      { return value; }
	public final String toString() { return key + "=" + value; }

	public final int hashCode() {
		//key的hash值与value的hash值的异或运算
		//异或运算: 0^0=0,0^1=1,1^0=1,1^1=0; 即: 参加运算的两个对象,如果两个相应位为“异”(值不同),则该位结果为1,否则为0
		return Objects.hashCode(key) ^ Objects.hashCode(value);
	}

	public final V setValue(V newValue) {
		V oldValue = value;
		value = newValue;
		return oldValue;  //在更新value后, 会返回旧的value值
	}

	public final boolean equals(Object o) {
		if (o == this)
			return true;
		if (o instanceof Map.Entry) {
			Map.Entry<?,?> e = (Map.Entry<?,?>)o;
			//key与value都需要根据各自定义的equals方法认定为相同
			if (Objects.equals(key, e.getKey()) &&
				Objects.equals(value, e.getValue()))
				return true;
		}
		return false;
	}
}

红黑树节点

注意: 红黑树节点继承链表节点, 因此在红黑树中是维护着链表的前后顺序的

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // 父节点
    TreeNode<K,V> left;    //左子节点
    TreeNode<K,V> right;   //右子节点
    TreeNode<K,V> prev;    // 维持红黑树中节点的添加顺序 (注意: 在删除节点后, 需要取消连接)
    boolean red;           // 红黑树节点颜色, 默认为红色
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
    }

    /**
     * 返回节点的根节点
     */
    final TreeNode<K,V> root() {
        for (TreeNode<K,V> r = this, p;;) {
            if ((p = r.parent) == null)
                return r;
            r = p;
        }
    }
}

4. HashMap节点的添加: put

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab;
    Node<K,V> p;
    int n, i;  //n: hash表的长度; i: 目标key在hash表中的索引
    if ((tab = table) == null || (n = tab.length) == 0)
       /**
        * 如果当前hash表为空, 或者长度为0 则初始化 【懒加载】
        */
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        /**
         * 如果对应hash桶的位置没有节点, 那么直接将当前节点放到此位置中
         * p为当前节点key的hash的索引值所对应的桶节点, 在判断语句中已经进行赋值操作
         */
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        /**
         * 如果当前桶节点的hash值与新增节点的hash值相同, 并且key也相同, 即覆盖当前节点的value即可
         */
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        /**
         * 【程序执行到这里】: 当前桶节点的key与新增节点的key不相同
         * 红黑树节点处理
         */
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //如果在红黑树中存在相同key的节点, 那么这里会返回该节点, 便于后面统一进行覆盖操作
        /**
         * 链表节点处理
         */
        else {
            for (int binCount = 0; ; ++binCount) {  //计算当前链表的长度, 后面判断如果大于8, 需要转为红黑树
                /**
                 * 如果查找至链表尾部, 还未找到相同key, 那么直接添加至尾部即可;
                 * 这个也就是为什么新增的节点需要插入到链表尾部, 反正都要到达链表尾部的
                 */
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    /**
                     * 这里为什么要对TREEIFY_THRESHOLD-1?
                     * 因为在本次插入节点后, 链表的长度已经增长了1
                     */
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                        /**
                         * 符合调条件, 当前链表可能需要转为红黑树
                         */
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                    /**
                     * 【当程序执行到这里】: 待插入节点与链表中的某个节点的key一致! 因为在并发场景中, 可能在插入新节点之前其他线程已经插入了一个与当前节点相同key的节点,
                     * 为了保证hashmap中key的唯一性, 所以需要做最后的校验
                     */
                    break;
                p = e;
            } //for
        }
        /**
         * 【当程序执行到这里】: 如果e不为null, 那么说明在hashmap中存在于待插入的节点相同的key, 那么只需要覆盖该节点的value值即可
         */
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                /**
                 * 如果允许覆盖旧的value 或者 旧的value为bull 才会进行覆盖
                 */
                e.value = value;
            afterNodeAccess(e);  //linkedHashMap实现
            return oldValue; //返回覆盖的旧value值
        }
    }
    ++modCount;
    /**
     * 在添加新的元素后, 判断是否超过阈值, 如果超过, 则进行扩容
     */
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);//linkedHashMap实现
    return null;
}

相关方法1: putTreeVal

/**
 *  向红黑树中添加元素
 */
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab, int h, K k, V v) {
    //map: 当前hashmap实例, tab: hash表, h: key的hash值, k: key值, v:value值
    Class<?> kc = null;
    boolean searched = false;  //是否进行了全局扫描搜索 (全局扫描的目的:是否已经存在即将插入的节点key)
    TreeNode<K,V> root = (parent != null) ? root() : this;  //确保传入当前节点为根节点
    /**
     * 从根节点开始遍历, 找到正确的插入位置
     */
    for (TreeNode<K,V> p = root;;) {
        int dir, ph; K pk; //dir: 新增节点与当前节点大小比较的差值, ph: 当前节点的hash值, pk: 当前节点的key, p:当前节点(待比较节点)
        /**
         * 新增节点key的hash值 小于 当前节点key的hash值, 说明新增节点需要在当前的左树中插入
         */
        if ((ph = p.hash) > h)
            dir = -1;
        /**
         * 新增节点key的hash值 大于 当前节点key的hash值, 说明新增节点需要在当前的右树中插入
         */
        else if (ph < h)
            dir = 1;
        /**
         *  如果新增节点key的hash值与当前节点的hash值相等, 那么需要比较key是否相同(比较两个key的地址以及 使用equals方法判定); 如果相同, 则返回该节点
         *  【注意】:此处没有覆盖操作, 这里仅仅返回重复节点, 在putVal方法中后面会统一进行覆盖value操作
         */
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))
            return p;
        /**
         * 【程序执行到这里】: 新增节点key的hash值与当前节点的hash值相等, 但是key并不相等
         *
         *  需要判断两个节点是否具有可比较性
         * 如果具有可比较性, 那么进行比较, 判断大小, 来决定接下来新增节点是在当前节点的左子树还是右子树中插入;
         */
        else if (
                (kc == null && (kc = comparableClassFor(k)) == null)  //两个节点不具备可比较性
                   ||
                (dir = compareComparables(kc, k, pk)) == 0) {         //两个节点具备可比较性, 但是通过比较方法判定两个节点的key大小相等
            /**
             * 对当前节点的左子树和右子树进行全局扫描, 判断新增节点key是否已经存在
             *   1.两个节点不具备可比较性
             *   2.两个节点具备可比较性, 但是通过比较方法判定两个节点的key大小相等
             *
             * 【注意】:此处比较大小, 只能比较两个key的大小, 不能判定两个key相同, 两个key相同只能使用equals()方法判定 或者 内存地址一致
             */
            if (!searched) {
                TreeNode<K,V> q, ch;
                searched = true;  // 全局扫描比较消耗性能, 所以只进行一次即可
                if (  ( (ch = p.left) != null && (q = ch.find(h, k, kc)) != null )  //在当前节点的左子树中查找是否存在新增节点的key
                        ||
                      ( (ch = p.right) != null && (q = ch.find(h, k, kc)) != null )   ) //在当前节点的右子树中查找是否存在新增节点的key
                    return q;
            }
            /**
             * 【程序执行到这里】: 此时新增节点已经明确不存在于当前红黑树中, 所以需要判断接下来需要插入到当前节点的左子树还是右子树中
             * 由于新增节点和当前节点不具有可比较性, 那么只能通过两个key的内存地址的hash值大小来比较
             */
            dir = tieBreakOrder(k, pk);
        }

        /**
         * 【程序执行到这里】:  已经确认新增节点需要向当前节点左子树中插入还是右子树中插入, 那么接下来开始准备下一轮的判断
         */
        TreeNode<K,V> xp = p;  //xp: 当前节点
        /**
         * 如果已定位至当前节点 ,那么需要根据新增节点与当前节点大小的差值, 判断新增节点为该叶子节点的左子节点还是右子节点
         */
        if ((p = (dir <= 0) ? p.left : p.right) == null) {
            Node<K,V> xpn = xp.next;  //xpn: 当前节点的下一个节点
            TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);  //根据key,value创建新节点, 准备插入, x: 新增节点
            if (dir <= 0)
                xp.left = x; //新增节点为当前节点的左子节点
            else
                xp.right = x;  //新增节点为当前节点的右子节点
            xp.next = x;  //新增节点为当前节点的下一个节点
            x.parent = x.prev = xp;  //新增节点的前一个节点和父节点都为当前节点
            if (xpn != null)  //维护原来当前节点的下一个节点的前一个节点为新增节点
                ((TreeNode<K,V>)xpn).prev = x;
            /**
             * 在插入节点后, 修复红黑树
             */
            moveRootToFront(tab, balanceInsertion(root, x));
            return null;
        }

    } //for循环结束
}

相关方法2: treeifyBin

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;  //n:hash表的长度, index:新增节点在hash表中索引位置, e:桶节点
    /**
     * 如果hash表为null 或者 hash表的长度大于等于最小红黑树容量 (也就是hash表中出现红黑树需要达到的最小容量)才会将链表转换为红黑树
     */
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        //如果小于, 则进行的是【扩容操作】, 而不是转换红黑树
        resize();
    /**
     * 符合转红黑树条件
     *    【1】:链表长度超过8
     *    【2】:hash表的长度大于最小红黑树容量64
     *
     *    (n - 1) & hash  ==> 取模,获取索引
     */
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        /**
         * 如果当前桶节点不为空
         * 前面已经判断过该桶节点不为空了, 此处为什么还要再次判断 ?
         * 因为, hashmap中有可能是存在并发操作....
         */
        TreeNode<K,V> hd = null, tl = null;  //td: 链表的第一个节点
        do {
            /**
             * 将链表中Node节点转为TreeNode节点
             * 由于之前是链表, 所以该链表的所有节点均为Node, 但是, 接下来需要转为红黑树了, 就不能再使用Node节点, 而是TreeNode节点
             */
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;  //获取到链表节点转红黑树节点后的第一个节点, 后面需要根据这个节点将链表转为红黑树
            else {
                p.prev = tl;  //维护红黑树节点中的prev属性
                tl.next = p;  //维护链表中的next属性
            }
            tl = p;
        } while ((e = e.next) != null);

        /**
         * 链表转红黑树
         */
        if ((tab[index] = hd) != null)
            hd.treeify(tab);  //this为链表的第一个节点
    }
}

相关方法3: treeify

/**
 * Forms tree of the nodes linked from this node.
 * @return root of tree
 */
final void treeify(Node<K,V>[] tab) {
    TreeNode<K,V> root = null; //初始化红黑树的根节点为null
    /**
     * 从链表的第一个节点开始遍历, 转换
     */
    for (TreeNode<K,V> x = this, next; x != null; x = next) {
        next = (TreeNode<K,V>)x.next; //next: 当前节点的下一个节点
        x.left = x.right = null;  //初始化当前节点左右子节点都为null
        /**
         * 如果root节点未确认, 那么将当前节点作为根节点, 并置为黑色(红黑树性质:根节点为黑色)
         */
        if (root == null) {
            x.parent = null;
            x.red = false;
            root = x;
        }
        /**
         * 【当程序执行到这里】: root节点已经确认, 第一次执行到else中时, x节点为此时root节点在链表中的下一个节点
         * 生成红黑树
         */
        else {
            K k = x.key;     //当前节点的key
            int h = x.hash;  //当前节点key的hash值
            Class<?> kc = null;  //可比较节点的类对象
            /**
             * 每次都需要从根节点开始找到合适的位置插入当前节点
             * 在查找过程中不需要判断当前节点是否与红黑树中的节点通过equals判定为相同
             */
            for (TreeNode<K,V> p = root;;) {
                int dir, ph;   //dir: 当前节点与根节点大小差值, ph: 根节点节点的hash值
                K pk = p.key;  //根节点节点的key
                /**
                 * 判断当前节点需要插入到根节点的左子树还是右子树中
                 */
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                /**
                 * 如果当前节点与根节点key的hash值相同, 则判断是否具有可比较性, 根据compareTo方法判断大小
                 */
                else if ( (kc == null && (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0 )
                    /**
                     * 如果当前节点与根节点不具备可比较性, 或者具备可比较性,但是被compareTo方法判定为大小相同
                     * 则比较两个节点key内存地址的hash值大小
                     */
                    dir = tieBreakOrder(k, pk);

                /**
                 * 【当程序执行到这里】: 已经判断了根节点与当前节点的大小, 接下来根据差值, 判断是向根节点的左子树中插入还是右子树中插入
                 */
                TreeNode<K,V> xp = p;
                /**
                 * 根据差值判断是向根节点的左子树是右子树中查找, 并且是否到达最底层, 无法再向下查找; 如果没有到达最底层, 那么需要在该树中继续向下查找
                 */
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    /**
                     * 找到合适的节点位置, 开始插入
                     * 注意在转为红黑树后, 节点中仍然维护着链表中原有的prev属性
                     */
                    x.parent = xp;
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    /**
                     * 在插入后,修复红黑树性质
                     */
                    root = balanceInsertion(root, x);
                    break;
                }
            }
        } //else

    } //for

    /**
     * 将红黑树的root节点移动到hash表相对应的索引位置
     */
    moveRootToFront(tab, root);
}

相关方法4: moveRootToFront

/**
 * Ensures that the given root is the first node of its bin.
 * 【1】 将红黑树的root节点移动到hash表相对应的索引位置
 * 【2】 将红黑树的root节点维护到链表的第一个节点
 * 通过moveRootToFront方法, 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) {
        // 计算索引
        int index = (n - 1) & root.hash;
        // 原链表的头节点
        TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
        /**
         * 如果该索引位置的头节点不是root节点,则该索引位置的头节点替换为root节点
         */
        if (root != first) {
            Node<K,V> rn;  //rn:后一个节点
            //将该索引位置的头节点赋值为root节点
            tab[index] = root;
            TreeNode<K,V> rp = root.prev; //root节点前一个节点
            /**
             * 删除root节点在原有链表中前后引用关系, 将root节点从链表中"删除", 因为后面要将root节点添加到链表的头部
             */
            // 如果root节点的next节点不为空,则将root节点的next节点的prev属性设置为root节点的prev节点
            if ((rn = root.next) != null)
                ((TreeNode<K,V>)rn).prev = rp;
            // 如果root节点的prev节点不为空,则将root节点的prev节点的next属性设置为root节点的next节点
            if (rp != null)
                rp.next = rn;
            /**
             * 将原有链表的第一个节点设为root节点的下一个节点
             */
            if (first != null)
                // 如果原头节点不为空, 则将原头节点的prev属性设置为root节点
                first.prev = root;
            // root的next指向头节点
            root.next = first;
            // root的prev指向null
            root.prev = null;
        }
        /**
         * 这一步是防御性的编程
         * 校验TreeNode对象是否满足红黑树和双链表的特性
         * 如果这个方法校验不通过:可能是因为用户编程失误,破坏了结构(例如:并发场景下);也可能是TreeNode的实现有问题(这个是理论上的以防万一);
         **/
        assert checkInvariants(root);
    }
}

 相关方法5:tableSizeFor

/**
 * Returns a power of two size for the given target capacity.
 * 找到【大于等于】给定容量的最小2的次幂值
 * 例如: cap = 15, return 16;
 * HashMap 中没有 capacity 属性, 
 * 初始化时,如果传了初始化容量值,该值是存在 threshold 变量,并且hash数组是在第一次 put 时才会进行初始化,
 * 初始化时会将此时的 threshold 值作为新表的 capacity 值,然后用 capacity 和 loadFactor 计算新表的真正 threshold 值
 * 而此方法就是根据threshold(临时作为capacity)计算出真正的capacity
 */
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;
}

相关方法6: putMapEntries

/**
 * Implements Map.putAll and Map constructor
 *
 * @param m the map
 * @param evict false when initially constructing this map, else
 * true (relayed to method afterNodeInsertion).
 *   将给定map的所有元素放入当前的map中, 给定的map必须实现Map.putAll方法和Map的构造函数
 */
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();  //给定map中的元素数量
    if (s > 0) {
        if (table == null) { // pre-size
            /**
             * 此处根据给定map中实际元素的数量, 推算容纳下给定map中所有元素所需要的最小容量
             * size <= threshold = capacity * 0.75
             * 如果想要capacity取得最小值, 那么threshold等于size即可, 因此可以通过可以通过size直接推算最小容量
             */
            float ft = ((float)s / loadFactor) + 1.0F;  //计算新的hash表能够容纳下给定map中所有元素所需要的最小capacity
            int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY);

            if (t > threshold)  //如果最小容量大于当前hash的阈值, 则更新阈值为大于等于当前容量的最小2的等次幂 例:s = 3, t = 5, threshold = 8
                /**
                 * 此处的threshold为新hash表的最小容量, 后面threshold将赋给newCap, 而threshold将根据newCap * 0.75重新计算
                 * 此处为什么要使用threshold来表示最小容量?
                 *   HashMap 有 threshold 属性和 loadFactor 属性,但是没有 capacity 属性。
                 *   初始化时,如果传了初始化容量值,该值是存在 threshold 变量,
                 *   并且 Node 数组是在第一次 put 时才会进行初始化,初始化时会将此时的 threshold 值作为新表的 capacity 值,
                 *   然后用 capacity 和 loadFactor 计算新表的真正 threshold 值。
                 *
                 * Hash表的容量必须为2的等次幂, 所以此处需要计算大于等于期望容量的最小值
                 */
                threshold = tableSizeFor(t);
        }
        else if (s > threshold)
            /**
             * 如果当前hash表不为null,且单单给定map中的元素就超过了阈值, 则提前扩容, 在遍历插入, 在插入的过程中也有可能进行扩容
             * 其他情况则在put的时候进行扩容
             */
            resize();
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { //遍历给定map,向当前map中put元素
            K key = e.getKey();
            V value = e.getValue();
            /**
             * 向目标hash表中添加元素
             */
            putVal(hash(key), key, value, false, evict);
        }
    }
}


 

5. HashMap节点的获取: get

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

/**
 *  根据key的hash值获取value
 */
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    /**
     * hash表不为空且长度大于0
     * 获取到根据hash计算索引,获取到第一个节点(该节点可能为链表节点或者红黑树根节点)
     */
    if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
        /**
         *  判断key是否相同的条件: 两个key的内存地址相同 或者 两个key通过equals方法判定为相同
         * 判断第一个节点是否是目标节点(注意:判断两个节点相同只能使用equals方法, 而compareTo()仅用于比较两个可比较节点的大小, 不能作为是否相同
         */
        if (first.hash == hash  &&  ((k = first.key) == key || (key != null && key.equals(k))) )
            return first;
        //如果头节点的下一个节点不为null才会去查找, 否则直接返回null
        if ((e = first.next) != null) {
            /**
             * 1.如果头节点为红黑树节点, 需要到红黑树找
             *   调用红黑树的方法获取并返回指定key的节点
             */
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            /**
             * 2. 如果头节点为链表, 循环遍历链表寻找目标节点
             */
            do {
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

相关方法1:getTreeNode

/**
 * Calls find for root node.
 */
final TreeNode<K,V> getTreeNode(int h, Object k) {
    /**
     * 找到当前节点的根节点, 然后开始查找指定节点
     */
    return ((parent != null) ? root() : this).find(h, k, null);
}

/**
 * 返回节点的根节点
 */
final TreeNode<K,V> root() {
    for (TreeNode<K,V> r = this, p;;) {
        if ((p = r.parent) == null)
            return r;
        r = p;
    }
}

相关方法2:find

/**
 * Finds the node starting at root p with the given hash and key.
 * The kc argument caches comparableClassFor(key) upon first use
 * comparing keys.
 *
 * 在红黑树中查找指定节点
 *
 *  注意: 在find方法中比较的是hash值大小, 在某棵红黑树中, 所有的节点在hash表中的索引是一样的, 但是hash值不一定相同
 */
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
    TreeNode<K,V> p = this;   //this: 当前节点, h:目标key的hash值, k:目标节点的key, kc:
    do {
        int ph, dir;  //ph: 当前节点的hash值; dir:
        K pk;  //当前节点的key
        TreeNode<K,V> pl = p.left, pr = p.right, q;  //pl当前节点的左子节点, pr当前节点的右子节点
        /**
         * 获取当前节点的hash值, 比较hash值
         * 如果目标key的hash小于当前节点key的hash值, 则向该红黑树的左子树查找
         */
        if ((ph = p.hash) > h)
            p = pl;
        /**
         * 如果目标key的hash大于当前节点key的hash值, 则向该红黑树的右子树查找
         */
        else if (ph < h)
            p = pr;
        /**
         *  【程序到达这里】: 当前节点与目标节点hash值相同, 开始判定key是否相同:
         *  key相同需要满足的条件:
         *     1.目标key的hash等于当前节点key的hash值
         *     2.当前节点的key与目标节点的key内存地址相同 或者 当前节点的key与目标节点的key通过equals判定为相同
         */
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))
            return p;
        /**
         * 【程序到达这里】: 当前节点与目标节点hash值相同, 但是key不相同, 也就是目标节点可能在当前的节点子树中;
         *   思考: 此时当前节点的hash值与目标节点的hash值相同, 但是又不是同一个节点, 那么
         *       接下来需要接着向下查找, 但是由于hash值相同, 将无法判断目标节点在当前的节点的
         *       左子树还是右子树中, 所以如果节点存在可比较性, 将根据compareTo方法进行比较;如
         *       若节点不存在可比较性, 那么将采用层序遍历, 向下逐个查找;
         *
         *  当前节点的左子节点为null,即不存在左子树, 那么将准备在当前节点的右子树中查找目标key
         */
        else if (pl == null)
            p = pr;
        /**
         *  当前节点的右子节点为null,即不存在右子树, 那么将准备在当前节点的左子树中查找目标key
         */
        else if (pr == null)
            p = pl;
        /**
         * 【程序到达这里】: 当前节点的左右子树都不为空, 那么需要判断节点是否具有可比较性
         */
        else if ( (kc != null || (kc = comparableClassFor(k)) != null) //判断key是否具有可比较性(是否实现Comparable接口)
                  &&
                 (dir = compareComparables(kc, k, pk)) != 0 )  //dir: 目标节点key与当前节点key比较的差值
            /**
             * 如果dir小于0, 说明目标节点key小于当前节点key,那么将开始向当前节点的左子树中查找
             * 如果dir大于0, 说明目标节点key大于当前节点key,那么将开始向当前节点的右子树中查找
             */
            p = (dir < 0) ? pl : pr;
        /**
         * 如果dir等于0, 说明目标节点key的大小等于当前节点key的大小, 但是不能判定为两个key相同,
         * 两个key相同只能通过内存地址,或者equals方法判断; 如果dir==0, 那么将开始分别向左右子树中查找;
         *
         * 默认向开始递归向当前节点的右子树中查找, 如果找到则直接返回, 如果没有找到,则开始向左子树中查找
         */
        else if ((q = pr.find(h, k, kc)) != null)
            return q;
        else
            p = pl;
    } while (p != null);
    return null;
}

6. HashMap的扩容:resize

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        /**
         *  旧hash表进行扩容
         */
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
            /**
             * 新容量与新阈值均扩展为原数值的两倍
             */
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        /**
         * 【程序执行到这里】: 目标hash表的旧容量为空, 但是旧阈值不为空, 也就说明此时是需要进行初始化的
         *  而旧的阈值oldThr 就是 提前计算好需要的hash表的初始化容量
         */
        newCap = oldThr;
    else {
        /**
         * 【程序执行到这里】: 目标hash表的旧容量和旧阈值均为空, 需要将容量和阈值设置为默认值
         */
        newCap = DEFAULT_INITIAL_CAPACITY;  //默认容量:16
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);  //默认阈值:12
    }

    if (newThr == 0) {
        /**
         * 【程序执行到这里】: 新的容量已经计算出来, 但是新的阈值还未进行计算
         */
        float ft = (float)newCap * loadFactor;  //新的容量 * 负载因子 = 新的阈值
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr; //更新阈值
    /**
     * 根据新的容量创建hash表
     */
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    /**
     * 如果就得hash表不为空, 那么需要进行元素挪动, 即需要把旧的hash表上的元素挪到新的hash表上
     */
    if (oldTab != null) {
        /**
         * 遍历旧的hash表的所有桶节点, 开始挪动元素
         */
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;  // e: 准备挪动的节点
            //如果该桶节点不为空,则开始挪动
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;  //清除原桶节点的引用
                if (e.next == null)
                    /**
                     * 如果桶节点的下一个节点为空, 那么说明该hash桶中只有一个节点, 直接计算新的索引, 然后挪动就可以了
                     *  注意:如果在旧的hash表中某个桶的位置只有一个元素, 那么在新的hash表中, 该元素所对应的新的hash桶位置也将只有一个元素
                     */
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    /**
                     * 如果当前节点是红黑树节点, 则强转为TreeNode节点, 利用红黑树性质挪动节点
                     */
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // 保持顺序
                    /**
                     * 如果当前节点是链表节点, 那么会根据索引位置将该链表拆为两个链表
                     */
                    Node<K,V> loHead = null, loTail = null;  //链表1
                    Node<K,V> hiHead = null, hiTail = null;  //链表2
                    Node<K,V> next;
                    do {
                        next = e.next;  //获取当前节点的下一个节点
                        /**
                         * 如果节点索引在扩容前后一致
                         */
                        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);
                    /**
                     * 将lo链表放到对应的hash桶中
                     */
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    /**
                     * 将hi链表放到对应的hash桶中
                     */
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

相关方法1:split

/**
 * Splits nodes in a tree bin into lower and upper tree bins,
 * or untreeifies if now too small. Called only from resize;
 * see above discussion about split bits and indices.
 *
 * 在扩容后, 红黑树中的节点,只会存在于两个位置: 原索引的位置, 原索引+旧容量
 * 为什么?
 *   假设:
 *   (1)某个节点key的hash值为 10(1010), hashMap旧容量为 4(100), 新容量为 8 (1000)
 *   hash:     1010
 *   oldCap-1:   11
 *   oldIndex:   10
 *
 *   hash:     1010
 *   newCap-1:  111
 *   newIndex:  010
 *
 *   oldIndex == newIndex
 *
 *   (2)某个节点key的hash值为 14(1110), hashMap旧容量为 4(100), 新容量为 8 (1000)
 *   hash:     1110
 *   oldCap-1:   11
 *   oldIndex:   10
 *
 *   hash:     1110
 *   newCap-1:  111
 *   newIndex:  110
 *
 *   oldIndex == newIndex + oldCap
 *
 *   如何判断索引是在原位置, 还是在原位置+oldCap呢?
 *   在上面的两个例子中, 可以看出, 新的索引在哪个位置取决于hash表中oldCap容量的最高位对应hash值的那一位是 0 还是 1
 *   也就是可以通过 hash与oldCap进行&运算, 可以计算出该位置是 0 还是 1, 如果是0, 则在原位置, 如果是1,则在原位置+oldCap
 *
 */
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
    //map: 当前hashmap对象, tab: 新的hash表, index:当前节点所在hash表中位置, bit:旧容量
    TreeNode<K,V> b = this;
    // Relink into lo and hi lists, 保持节点顺序
    TreeNode<K,V> loHead = null, loTail = null;  //存储索引位置为"原索引位置"的节点  (注意链表的节点为TreeNode)
    TreeNode<K,V> hiHead = null, hiTail = null;  //存储索引位置为"原索引位置+旧容量"的节点  (注意链表的节点为TreeNode)
    int lc = 0, hc = 0;
    for (TreeNode<K,V> e = b, next; e != null; e = next) {  //遍历整个红黑树节点, 移动节点到新位置上
        next = (TreeNode<K,V>)e.next;  // next为当前节点的下一个节点
        e.next = null;  //先将当前节点的next属性置空
        /**
         * 节点索引在扩容后前后一致
         */
        if ((e.hash & bit) == 0) {
            if ((e.prev = loTail) == null)  //如果loTail为空, 说明是第一个节点
                loHead = e;   //给头节点赋值
            else
                loTail.next = e;  //向链表尾部添加节点
            loTail = e;  //更新链表尾部节点
            ++lc;  //统计原索引位置节点的数量
        }
        /**
         * 节点索引在扩容后, 新的位置为原位置+旧容量
         */
        else {
            if ((e.prev = hiTail) == null) //如果hiTail为空, 说明是第一个节点
                hiHead = e;   //给头节点赋值
            else
                hiTail.next = e;    //向链表尾部添加节点
            hiTail = e;  //更新链表尾部节点
            ++hc;   //统计新索引位置节点的数量
        }
    }//for

    /**
     * 【当程序执行到这里】: 原红黑树已经拆分为两个链表, 如果链表不为null, 那么该链表将更新到hash桶对应位置上
     *  接下来需要根据每个链表的长度, 判断时候需要将红黑树装化为链表;
     */
    if (loHead != null) {
        if (lc <= UNTREEIFY_THRESHOLD)
            /**
             * 如果链表长度小于等于UNTREEIFY_THRESHOLD, 那么不需要转为红黑树, 保持为链表即可
             * 那为什么这里调用了红黑树的untreeify方法, 因为在上面维护两个链表的节点是TreeNode, 这里需要转为链表节点Node, 节省内存使用
             */
            tab[index] = loHead.untreeify(map);
        else {
            /**
             * 该链表需要转为红黑树, 那么先将链表赋给对应的hash桶中
             */
            tab[index] = loHead;
            if (hiHead != null)
                /**
                 * 如果hiHead不为null, 才会转为红黑树
                 * 因为, 如果hi链表为null, 说明在扩容前后, 红黑树上节点的位置在红黑树中没有发生任何改变, 并且该红黑树仍然在原hash桶位置
                 *      如果hi链表不为null, 说明hi链表分走了一部分节点, 但是hi链表仍然没有到达转为链表的条件, 所以需要转为红黑树
                 */
                loHead.treeify(tab);
        }
    }
    /**
     * 处理hi链表
     */
    if (hiHead != null) {
        if (hc <= UNTREEIFY_THRESHOLD)
            /**
             * 如果链表长度小于等于UNTREEIFY_THRESHOLD, 那么不需要转为红黑树, 保持为链表即可
             * 那为什么这里调用了红黑树的untreeify方法, 因为在上面维护两个链表的节点是TreeNode, 这里需要转为链表节点Node, 节省内存使用
             *
             * 更新链表位置在索引+旧容量
             */
            tab[index + bit] = hiHead.untreeify(map);
        else {
            tab[index + bit] = hiHead;
            if (loHead != null)
                /**
                 * 如果loHead不为null, 才会转为红黑树
                 * 因为: 如果lo链表为null, 说明在扩容前后, 红黑树上节点的位置在红黑树中没有发生任何改变, 仅需要修改根节点在hash桶中的位置,
                 *       如果lo链表不为null, 说明lo链表分走了一部分节点, 但是hi链表仍然没有到达转为链表的条件, 所以需要转为红黑树
                 */
                hiHead.treeify(tab);
        }
    }
}

7.HashMap中节点的删除:remove

public V remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}

final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) {
    //tab:hash表, p:key所在hash桶的第一个节点(链表的首节点或者红黑树的根节点), n: 表的长度, index:索引
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    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;
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            /**
             * 如果hash桶中的第一个节点就是待删除节点
             */
            node = p;
        else if ((e = p.next) != null) {
            /**
             * 待删除节点存在于链表或者红黑树中
             */
            if (p instanceof TreeNode)
                /**
                 * 待删除节点是红黑树节点,则找到这个节点
                 */
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            else {
                /**
                 * 遍历链表, 找到待删除的节点
                 */
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e; //p为前一个节点, e为即将要查找的节点
                } while ((e = e.next) != null);
            }
        }
        /**
         * 【当程序执行到这里】: node为待删除的节点, 可能为红黑树节点, 也可能为链表节点
         * 删除操作的前置条件:
         *    (1)待删除节点不为空
         *    (2)需要匹配value相同 并且 查找到的value和给定的value一致(内存地址一致或者equals判定为一致)
         *        或者
         *       不匹配value, 只要key相同就删除
         */
        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)
                /**
                 * 如果hash桶中的第一个节点就是待删除节点
                 */
                tab[index] = node.next;
            else
                /**
                 * 如果是链表节点
                 * p为待删除节点的前一个节点, node为待删除节点
                 * 删除单向链表节点, 只修改next引用即可, 待删除节点的前一个节点的next属性指向待删除节点的下一个节点
                 */
                p.next = node.next;
            ++modCount;
            --size;  //size减一
            afterNodeRemoval(node);  //linkedHashMap实现
            return node;
        }
    }
    return null;
}

相关方法1: removeTreeNode

final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab, boolean movable) {
    /**
     * this: 待删除节点, movable:如果为false, 则在移除结构时不要移动其他节点
     */
    int n;
    if (tab == null || (n = tab.length) == 0)
        return;
    int index = (n - 1) & hash;  //index:计算待删除节点在hash表中的索引, hash:为待删除节点的hash值
    TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;  //root:红黑树的根节点, rl:红黑树的左子节点
    TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;  //succ:链表属性的中下一个节点, pred:红黑树属性中的上一个节点
    if (pred == null)  //如果上一个节点为空,说明删除的节点为红黑树的根节点
        tab[index] = first = succ;
    else
        pred.next = succ;  //如果不是根节点, 那么将待删除节点从链表引用中"移除", 待删除节点的前一个节点的next指向删除节点的下一个节点
    if (succ != null)
        succ.prev = pred;  //待删除节点的后一个节点的prev指向删除节点的上一个节点
    if (first == null)  //如果此时first节点为null, 那么说明该索引位置已经没有节点了, 直接返回
        return;
    if (root.parent != null)
        root = root.root();  //找到根节点
    if (root == null || root.right == null ||
        (rl = root.left) == null || rl.left == null) {
        tab[index] = first.untreeify(map);  // 通过根节点判断当前红黑树是否太小了, 如果是,则转为链表
        return;
    }
    /**
     * 上面链表的引用已维护完成, 接下来开始维护红黑树的相关引用
     */
    TreeNode<K,V> p = this, pl = left, pr = right, replacement;  //p:待删除的节点, pl:左子节点, pr:右子节点
    /**
     * 待删除节点的度为 2, 即:红黑树的左右子节点都不为null, 需要找后继节点, 然后让后继节点替换待删除节点, 最终删除的还是度为0或者1的后继节点
     * 因此: 可得出结论, 在删除节点时,最终被删除的都是度为1或者0的节点
     * 如何找到后继节点?  当前节点右子节点的左子树最小的节点
     */
    if (pl != null && pr != null) {
        TreeNode<K,V> s = pr, sl;  //pr:待删除节点的右子节点, sl:待删除节点的右子节点的左子节点, s:待删除节点的后继节点, 也就是真正被删除的节点,
        /**
         *  1. 循环找到后继节点
         */
        while ((sl = s.left) != null)
            s = sl;

        /**
         * 交换待删除节点与真正被删除节点(待删除节点的后继节点) 的颜色
         */
        boolean c = s.red; s.red = p.red; p.red = c;
        /**
         * 2. 找到后继节点被删除后的替代节点
         * (当后继节点的度为1时, 在后继节点被删除后, 需要让后继节点的右子节点替代后继节点)
         */
        TreeNode<K,V> sr = s.right;  // 后继节点的右子节点 (后继节点要么只有一个右子节点, 要么没有子节点)
        TreeNode<K,V> pp = p.parent;  //待删除节点的父节点
        /**
         * 如果后继节点为待删除节点的右子节点, 那么直接让后继节点替换父节点引用关系即可
         */
        if (s == pr) {
            p.parent = s;
            s.right = p;
        }
        else {  //如果后继节点为待删除节点的右子树中的某个节点的左子节点, 替换后继节点与待删除节点的引用关系
            TreeNode<K,V> sp = s.parent;  //后继节点的父节点
            /**
             * 【将待删除节点"移动"到后继节点的位置, 让后继节点"移动"到待删除节点的位置】, 维护后继节点的父节点与待删除节点的引用关系
             */
            if ((p.parent = sp) != null) {
                if (s == sp.left)
                    sp.left = p;  //如果后继节点是父节点的左子树
                else
                    sp.right = p;//如果后继节点是父节点的右子树  (疑问? 后继节点不可能为待删除节点右子树中的右子节点吧...)
            }
            /**
             * 维护后继节点的右子节点引用
             * 将待删除节点的右子节点作为后继节点的右子节点, 并且将待删除节点的右子节点的parent属性引用改为后继节点
             */
            if ((s.right = pr) != null)
                pr.parent = s;
        }

        /**
         * 维护后继节点与待删除节点的父节点和子节点的引用关系
         */
        p.left = null;  // 替换后的p的左子节点为null
        if ((p.right = sr) != null)  //如果后继节点的右子节点不为空, 维护右子节点与待删除节点引用关系
            sr.parent = p;
        if ((s.left = pl) != null)   //维护待删除节点左子树的引用关系
            pl.parent = s;
        if ((s.parent = pp) == null)  //将后继节点的parent属性指向待删除节点的父节点
            root = s;
        else if (p == pp.left)
            pp.left = s;       //如果待删除节点为左子节点, 那么需要将待删除节点的父节点的left属性指向后继节点
        else
            pp.right = s;      //如果待删除节点为右子节点, 那么需要将待删除节点的父节点的right属性指向后继节点

        /**
         * 【当程序执行到这里】:待删除节点"移动"到后继节点的位置, 后继节点"移动"到待删除节点的位置
         * replacement: 在后继节点被删除后, 替代后继节点的节点
         */
        if (sr != null)
            replacement = sr;  // 如果后继节点的右子树不为空, 那么可以理解为删除一个度为 1的根节点场景
        else
            replacement = p;   // 如果后继节点的右子树为空, 那么可以理解为删除一个度为0的根节点场景
    } //if
    else if (pl != null)
        replacement = pl; //待删除节点的度为 1
    else if (pr != null)
        replacement = pr; //待删除节点的度为 1
    else
        replacement = p;  //待删除节点的度为 0
    /**
     * 如果在移动后, 原后继节点(现待删除节点)的度为 1, 让replacement替换待删除节点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的所有引用全部删除
         */
        p.left = p.right = p.parent = null;
    }
    /**
     * 如果待删除节点的颜色是红色, 不需要调整红黑树的性质
     */
    TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);

    /**
     * 删除待删除节点为度为0的节点
     */
    if (replacement == p) {  // detach
        TreeNode<K,V> pp = p.parent;  //获取待删除节点父节点
        /**
         * 移除操作, 移除删除节点的parent引用和父节点的左子引用或者右子引用关系
         */
        p.parent = null;
        if (pp != null) {
            if (p == pp.left)
                pp.left = null;
            else if (p == pp.right)
                pp.right = null;
        }
    }
    if (movable)
        //移动根节点到hash桶中
        moveRootToFront(tab, r);
}

8.HashMap中红黑树相关方法

注意: 如果对红黑树不太理解可以先看着两篇播客(学习红黑树一定要先学会b树)

1. B-树

2. 红黑树

左旋转: rotateLeft

/**
 * 左旋转
 * 左旋转后发生了什么?
 *
 *  pp                  pp
 *  p         ==>       r
 *     r            p       rr
 *  rl   rr           rl
 */
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p) {
    TreeNode<K,V> r, pp, rl;   //r:p的右子节点, pp:p的父节点, rl: 右子节点的左子节点, rr:r节点的右子节点, 在旋转过程中, 其引用并未发生变化
    if (p != null && (r = p.right) != null) {
        if ((rl = p.right = r.left) != null)  //把r节点的左子节点移动到p的右子节点位置
            rl.parent = p;  //如果rl节点不为null, 那么需要维护一下rl的parent引用
        if ((pp = r.parent = p.parent) == null)  //将r节点的父节点更新为p的父节点pp, 如果p节点为根节点,那么需要染为黑色
            (root = r).red = false;  //r染为黑色
        else if (pp.left == p) //如果p节点是左子节点, 那么pp节点的左子节点更新为r节点
            pp.left = r;
        else
            pp.right = r;
        r.left = p;  //更新r节点左子节点为p
        p.parent = r;   //更新p的父节点为r
    }
    return root;
}

右旋转: rotateRight

/**
 * 右旋转
 * 右旋转后发生了什么?
 *
 *       pp              pp
 *       p     ==>       l
 *    l              ll       p
 * ll   lr                 lr
 */
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p) {
    TreeNode<K,V> l, pp, lr;   //l:p的右节点, pp:p的父节点, lr: l的右节点, ll:l的左节点, 因为在整个旋转过程中引用关系并没有发生改变
    if (p != null && (l = p.left) != null) {
        if ((lr = p.left = l.right) != null)  //如果lr节点不为null, 那么lr节点将作为p的左节点
            lr.parent = p;   //如果lr不为null, 那么更新lr的parent属性
        if ((pp = l.parent = p.parent) == null)  //将l节点的更新为p的父节点
            (root = l).red = false;  //如果p为根节点, 那么l节点需要置为黑色
        else if (pp.right == p)  //判断p为左子节点还是右子节点, 更新pp的left或者right属性
            pp.right = l;
        else
            pp.left = l;
        l.right = p;   //更新l的right属性
        p.parent = l;  //更新p的parent属性为l节点
    }
    return root;
}

插入后调整红黑树平衡:balanceInsertion

/**
 *  在红黑树插入节点后, 修复红黑树的性质
 *  root: 红黑树的新节点, x为新插入的节点
 *
 *【红黑树添加节点的12种情况】
 * 一些节点的定义:
 *      x:新增节点
 *      xp:新增节点的父节点
 *      xu: 新增节点的叔叔节点
 *      xpp:新增节点的祖父节点
 *      xppl:新增节点的祖父节点的左子节点 (新增节点的父节点或者叔叔节点)
 *      xppr:新增节点的祖父节点的右子节点 (新增节点的父节点或者叔叔节点)
 *
 * 【1】  xp节点为黑色, xp节点度为 0, x节点为左子节点  ==> 无需调整
 * 【2】  xp节点为黑色, xp节点度为 0, x节点为右子节点  ==> 无需调整
 * 【3】  xp节点为黑色, xp节点度为 1, x节点为左子节点  ==> 无需调整
 * 【4】  xp节点为黑色, xp节点度为 1, x节点为右子节点  ==> 无需调整
 *                                                                                               注意:这里的xp节点为旋转之后的xp节点, x节点为旋转之后的x节点
 * 【5】  xp节点为红色, xp节点度为 0, xp节点为(左)子节点, xpp节点为黑色, xpp节点度为 (1), x节点为(左)子节点  ==> xp节点右旋转, xp染为黑色, xpp染为红色
 * 【6】  xp节点为红色, xp节点度为 0, xp节点为(左)子节点, xpp节点为黑色, xpp节点度为 (1), x节点为(右)子节点  ==> xp节点先左旋转, 再右旋转, x染为黑色, xpp染为红色
 * 【7】  xp节点为红色, xp节点度为 0, xp节点为(右)子节点, xpp节点为黑色, xpp节点度为 (1), x节点为(左)子节点  ==> xp节点先右旋转, 再左旋转, x染为黑色, xpp染为红色
 * 【8】  xp节点为红色, xp节点度为 0, xp节点为(右)子节点, xpp节点为黑色, xpp节点度为 (1), x节点为(右)子节点  ==> xp节点左旋转, xp染为黑色, xpp染为红色
 *
 * 【9】  xp节点为红色, xp节点度为 0, xp节点为(左)子节点, xpp节点为黑色, xpp节点度为 (2), x节点为(左)子节点  ==> xp染为黑色, xu染为黑色, xpp染为红色, 然后以 xpp为新增节点x继续修复红黑树性质
 * 【10】 xp节点为红色, xp节点度为 0, xp节点为(左)子节点, xpp节点为黑色, xpp节点度为 (2), x节点为(右)子节点  ==> xp染为黑色, xu染为黑色, xpp染为红色, 然后以 xpp为新增节点x继续修复红黑树性质
 * 【11】 xp节点为红色, xp节点度为 0, xp节点为(右)子节点, xpp节点为黑色, xpp节点度为 (2), x节点为(左)子节点  ==> xp染为黑色, xu染为黑色, xpp染为红色, 然后以 xpp为新增节点x继续修复红黑树性质
 * 【12】 xp节点为红色, xp节点度为 0, xp节点为(右)子节点, xpp节点为黑色, xpp节点度为 (2), x节点为(右)子节点  ==> xp染为黑色, xu染为黑色, xpp染为红色, 然后以 xpp为新增节点x继续修复红黑树性质
 *
 * ##为什么没有父节点为黑色, 父节点度为 2的情况 ?
 *  因为如果父节点为2, 那么还哪还有位置插入新的节点... 红黑树也是一个二叉树
 *
 * ##为什么没有父节点为红色, 父节点度不为 0 的情况 ?
 *  因为如果父节点为红色,且度不为 0, 那么这棵红黑树将不满足性质4 "RED节点的子节点都是BLACK"
 *  具体原因可以按照红黑树推导4阶B树分析:
 *       如果父节点为红色, 那么该节点将跟它的黑色节点合为一个B树节点, 如果 父节点为红色,且度不为 0, 那么红黑树将无法推导为一个标准的4阶B树
 */
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) {
    /**
     * 默认插入的新节点为红色
     * 为什么是红色呢 ?
     *   因为默认为红色,可以满足红黑树五大性质中的四条,"RED节点的子节点都是BLACK" 这条除外
     */
    x.red = true;
    for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
        /**
         * xp:新增节点的父节点
         * xu:新增节点的叔叔节点
         * xpp:新增节点的祖父节点
         * xppl:新增节点的祖父节点的左子节点 (新增节点的父节点或者叔叔节点)
         * xppr:新增节点的祖父节点的右子节点 (新增节点的父节点或者叔叔节点)
         */
        if ((xp = x.parent) == null) {
            x.red = false; //如果新增节点的父节点为空, 那么新增节点为根节点, 红黑树中根节点必须为黑色, 直接返回即可
            return x;
        }
        /**
         * 添加节点不为根节点情况
         */
        else if (!xp.red || (xpp = xp.parent) == null)
            /**
             * xp节点为黑色, 或者父节点为root节点(黑色), 不需要调整
             *  情况:【1】【2】【3】【4】
             */
            return root;

        if (xp == (xppl = xpp.left)) {
            /**
             * xp节点为左子节点
             */
            if ((xppr = xpp.right) != null && xppr.red) {
                /**
                 * [xp节点为左子节点], xpp节点的度为 2, xu节点为红色
                 * 注意: 此时xu节点必为红色, 如果为黑色,则该红黑树将不满则性质"从任一节点到叶子节点的所有路径都包含相同数目的 BLACK节点"
                 * 情况:【9】【10】
                 */
                xppr.red = false;  //xu节点染为黑色
                xp.red = false;    //父节点染为黑色
                xpp.red = true;    // 祖父节点染为红色
                x = xpp;    // 将xpp节点当做新增节点,调整红黑树平衡 (4阶B树中的上溢)
            }
            else {
                /**
                 * [xp节点为左子节点], xpp节点的度为 1
                 * 情况【6】
                 */
                if (x == xp.right) {
                    /**
                     * [xp节点为左子节点, xpp节点的度为 1], x节点为右子节点(1)  ==>左旋转
                     */
                    root = rotateLeft(root, x = xp);  //先进行左旋转
                    //左旋之后,需要更新xp和xpp的引用节点为: 现xp节点为原x节点, 现x节点为原xp节点, xpp节点为现xp节点(原x节点)的父节点
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }


                if (xp != null) {
                    /**
                     * [xp节点为左子节点, xpp节点的度为 1], x节点为左子节点
                     * 情况:【5】
                     * [xp节点为左子节点, xpp节点的度为 1], x节点为右子节点(2)  ==>右旋转
                     * 情况:【6】
                     */
                    xp.red = false;               //xp节点(原x节点)染为黑色
                    if (xpp != null) {
                        xpp.red = true;           //xpp节点染为红色
                        root = rotateRight(root, xpp);  //再进行右旋转
                    }
                }
            } //else
        }

        /**
         * xp节点为右子节点
         */
        else {
            if (xppl != null && xppl.red) {
                /**
                 * [xp节点为右子节点], xpp节点度为 2, xu节点为红色
                 * 情况【11】【12】
                 */
                xppl.red = false;  //xu节点染为黑色
                xp.red = false;    //xp节点然为黑色
                xpp.red = true;    //xpp染为红色
                x = xpp;   // 将xpp节点当做新增节点,调整红黑树平衡 (4阶B树中的上溢)
            }
            else {
                /**
                 * [xp节点为右子节点], xpp节点度为 1
                 */
                if (x == xp.left) {
                    /**
                     * [xp节点为右子节点, xpp节点度为 1], x节点为左子节点(1)
                     */
                    root = rotateRight(root, x = xp);  //右旋转
                    //右旋之后,需要更新xp和xpp的引用节点为: 现xp节点为原x节点, 现x节点为原xp节点, xpp节点为现xp节点(原x节点)的父节点
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                if (xp != null) {
                    /**
                     *  [xp节点为右子节点, xpp节点度为 1], x节点为右子节点
                     *  情况:【8】
                     *  [xp节点为右子节点, xpp节点度为 1], x节点为左子节点(2)
                     *  情况:【7】
                     */
                    xp.red = false;  // xp染为黑色
                    if (xpp != null) {
                        xpp.red = true; //xpp染为红色
                        root = rotateLeft(root, xpp);
                    }
                }
            }
        }
    }
}

删除后调整红黑树平衡:balanceDeletion

/**
 * 在红黑树删除节点后, 修复红黑树
 */
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, TreeNode<K,V> x) {
    /**
     * x:   待移除节点被删除后的替换节点
     * xp:  父节点
     * xpl: 父节点的左子节点
     * xpr: 父节点的右子节点
     * 【当成程序执行到这里】: 如果待删除节点是度不为0的节点, 那么此节点已经被删除, 而顶替节点也已经替代了已经删除节点, 由于此时红黑树可能已经失去平衡, 所以需要进行维护
     */
    for (TreeNode<K,V> xp, xpl, xpr;;)  {
        if (x == null || x == root)
            /**
             * 如果x节点现在是根节点, 则无需调整
             */
            return root;
        else if ((xp = x.parent) == null) {
            /**
             * 替换节点为根节点, 染为黑色
             */
            x.red = false;
            return x;
        }
        /**
         * 【删除场景 1】:被删除节点存在一个红色的子节点,在删除后, 该子节点替代被删除节点的位置, 所以改为黑色即可
         * 如果替换节点为红色; 那么仅需要染为黑色, 红黑树就达到了平衡, 退出循环
         */
        else if (x.red) {
            x.red = false;
            return root;
        }
        /**
         *  [x节点为左子点] 因为x节点所处位置不一样, 后面的旋转方式也不一样
         * 【删除场景 2】:被删除节点为黑色叶子节点 (即:x节点的度为0, 不存在可以替换的子节点)
         * 【程序执行到这里】: 此时x节点还没有删除, 需要先调整红黑树性质, 再删除
         */
        else if ((xpl = xp.left) == x) {

            if ((xpr = xp.right) != null && xpr.red) {
                /**
                 * 【删除场景 2.1】:被删除节点为黑色叶子节点, 并且兄弟节点为红色, 需要将兄弟节点转为黑色
                 */
                xpr.red = false;  //兄弟节点染为黑色
                xp.red = true;    //父节点染为红色
                root = rotateLeft(root, xp);  //在经过左旋转后, x,xp节点的情况为:x节点为左子节点, xp节点度为 2, 兄弟节点为黑色
                xpr = (xp = x.parent) == null ? null : xp.right;
            }
            /**
             * 【删除场景 2.2】:被删除节点为黑色叶子节点, 并且兄弟节点为黑色
             */
            if (xpr == null)
                x = xp;
            else {
                TreeNode<K,V> sl = xpr.left, sr = xpr.right;
                if ((sr == null || !sr.red) && (sl == null || !sl.red)) {
                    /**
                     * 【删除场景 2.2.1】:被删除节点为黑色叶子节点, 并且兄弟节点为黑色,兄弟节点 没有 红色子节点
                     */
                    xpr.red = true; //兄弟节点染为红色, 第一次执行到此处时:不用更新x(p)节点的颜色, 后面p节点会被删除
                    x = xp;        // 把xp赋给x, 把xp当做被删除的节点, 继续向上修复红黑树 (基于4阶B树,出现下溢情况)
                }
                else {
                    /**
                     * 【删除场景 2.2.2】:被删除节点为黑色叶子节点, 并且兄弟节点为黑色,兄弟节点 有 红色子节点
                     */
                    if (sr == null || !sr.red) {
                        if (sl != null)
                            sl.red = false;  //如果兄弟节点的另一个左子节点不为空, 染为黑即可, 在右旋转之后, sl节点将作为新xp节点的左子节点
                        xpr.red = true;  //兄弟节点染红, 因为兄弟节点会替代原xp节点, 而原xp节点为红色
                        root = rotateRight(root, xpr); //右旋转
                        xpr = (xp = x.parent) == null ? null : xp.right;
                    }
                    /**
                     * 兄弟节点不为空,则兄弟节点需要继承原xp节点的颜色
                     */
                    if (xpr != null) {
                        xpr.red = (xp == null) ? false : xp.red;
                        if ((sr = xpr.right) != null)
                            sr.red = false;
                    }
                    /**
                     * 如果原xp节点不为null, 则需要将原xp节点染为黑色
                     */
                    if (xp != null) {
                        xp.red = false;
                        /**
                         * 如果兄弟节点有两个红色节点, 那么还需要一次右旋,恢复平衡
                         */
                        root = rotateLeft(root, xp);
                    }
                    x = root;
                }
            }
        }
        /**
         *   [x节点为右子点] 因为x节点所处位置不一样, 后面的旋转方式也不一样
         * 【删除场景 2】:被删除节点为黑色叶子节点 (即:x节点的度为 0, 不存在可以替换的子节点)
         * 【程序执行到这里】: 此时x节点还没有删除, 需要先调整红黑树性质, 再删除
         */
        else { // symmetric
            /**
             * 【删除场景 2.1】:被删除节点为黑色叶子节点, 并且兄弟节点为红色, 需要将兄弟节点转为黑色
             */
            if (xpl != null && xpl.red) {
                xpl.red = false;  //兄弟节点染为黑色
                xp.red = true;    //父节点染为红色
                root = rotateRight(root, xp);  //以xp进行右旋转, 旋转之后, 原兄弟节点为原xp节点的父节点
                xpl = (xp = x.parent) == null ? null : xp.left;
            }
            /**
             * 【删除场景 2.2】:被删除节点为黑色叶子节点, 并且兄弟节点为黑色
             */
            if (xpl == null)
                x = xp;
            else {
                TreeNode<K,V> sl = xpl.left, sr = xpl.right;  //sl:兄弟节点的左子节点, sr:兄弟节点右子节点
                if ((sl == null || !sl.red) && (sr == null || !sr.red)) {
                    /**
                     * 【删除场景 2.2.1】:被删除节点为黑色叶子节点, 并且兄弟节点为黑色,兄弟节点 没有 红色子节点
                     */
                    xpl.red = true;   //兄弟节点染为红色, 第一次执行到此处时:不用更新x(p)节点的颜色, 后面p节点会被删除
                    x = xp;           // 把xp赋给x, 把xp当做被删除的节点, 继续向上修复红黑树 (基于4阶B树,出现下溢情况)
                }
                else {
                    /**
                     * 【删除场景 2.2.2】:被删除节点为黑色叶子节点, 并且兄弟节点为黑色,兄弟节点 有 红色子节点
                     * [x节点为右子点]
                     */
                    if (sl == null || !sl.red) {  //兄弟节点的右子节点为红色 (根据【删除场景 2.2.1】条件推算)
                        if (sr != null)
                            sr.red = false;
                        xpl.red = true;
                        root = rotateLeft(root, xpl);  //左旋转, sr节点替环xp节点, xp节点替换x节点  (在旋转后:sr为父节点,xp为右子节点, sl为左子节点)
                        xpl = (xp = x.parent) == null ? null : xp.left;  //更新xpl节点
                    }
                    /**
                     * 兄弟节点不为空,则兄弟节点需要继承原xp节点的颜色
                     */
                    if (xpl != null) {
                        xpl.red = (xp == null) ? false : xp.red;
                        if ((sl = xpl.left) != null)
                            sl.red = false;  //sl节点染为黑色
                    }
                    /**
                     * 如果原xp节点不为null, 则需要将原xp节点染为黑色
                     */
                    if (xp != null) {
                        xp.red = false;
                        /**
                         * 如果兄弟节点有两个红色节点, 那么还需要一次右旋,恢复平衡
                         */
                        root = rotateRight(root, xp);
                    }
                    x = root;
                }//else
            }
        }
    } //for
}

检查红黑树: checkInvariants

/**
 * 验证红黑树的准确性
 */
static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
    // tp:父节点, tl:左子节点, tr:右子节点, tb:前驱节点, tn:后继节点
    TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
        tb = t.prev, tn = (TreeNode<K,V>)t.next;
    /**
     * 当出现以下任一情况时, 红黑树或者双向链表不正确
     */

    /**
     * 1、如果前驱节点存在, 但是前驱节点的后继节点不是当前节点
     */
    if (tb != null && tb.next != t)
        return false;
    /**
     * 2、如果后继节点存在, 但是后继节点的前驱节点不是当前节点
     */
    if (tn != null && tn.prev != t)
        return false;
    /**
     * 3、父节点存在, 但是父节点的左子节点、右子节点均不是当前节点
     */
    if (tp != null && t != tp.left && t != tp.right)
        return false;
    /**
     * 4、左子节点存在, 但是左子节点的父节点不是当前节点或者左子节点的hash值大于当前节点的hash值
     */
    if (tl != null && (tl.parent != t || tl.hash > t.hash))
        return false;
    /**
     * 5、右子节点存在, 但是右子节点的父节点不是当前节点或者右子节点的hash值小于当前节点的hash值
     */
    if (tr != null && (tr.parent != t || tr.hash < t.hash))
        return false;
    /**
     * 6、当前节点是红色,孩子节点也是红色  ==>红黑树性质
     */
    if (t.red && tl != null && tl.red && tr != null && tr.red)
        return false;
    /**
     * 递归验证左子树
     */
    if (tl != null && !checkInvariants(tl))
        return false;
    /**
     * 递归验证右子树
     */
    if (tr != null && !checkInvariants(tr))
        return false;
    /**
     * 通过验证, 返回true
     */
    return true;
}
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值