JDK 1.8 HashMap 源码分析(一)

空闲时间,根了一下最新HashMap源码,这里记录一下。如有错漏,请指正。

篇幅太长,因此这里分了两篇文章。

主要从以下功能切入:

  • 构造函数
  • put
  • get
  • remove

构造函数:

//默认构造函数,默认初始容量为16 ,默认加载因子为0.75
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; 
}

//指定初始容量的构造函数,默认加载因子为0.75
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

//指定初始容量和加载因子的构造函数
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    //若初始容量大于最大容量,则初始容量为最大容量
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    
    //若加载因子小于0或NaN即Not a number(0.0f/0.0f的值就是NaN)的话,抛出异常
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    //初始化阈值,该值用于判断何时需要扩容(resize)
    this.threshold = tableSizeFor(initialCapacity);
}


/**
* 该方法用于保证数组长度为2的n次方
* 
* 问: 为何数组长度一定要为2的n次方?
* 我们知道2的n次方=1+n个0,而2的n次方-1  实际就是n个1
* 在HashMap中,我们需要根据key的hash值取数组中的下标,为了较少碰撞(即key在table中位置相同),每个key在数 * 组中的下标可以通过以下公式获取:
* int index = key.hash % table.length;
* 但是由于%运算效率不高,因此使用效率高的与运算来取代%运算
* int index =  (table.length - 1) & key.hash; 
* 这里又要回到为什么要保证数组长度为2的n次方,因为2的n次方-1=n个1,例如这里数组长度为2⁴=16,那么:
* int index = 1111 & key.hash; 
* 这样既保证了均匀性,index也不会超过数组长度,同时效率也提高了。
* 两种运算效率的对比可自行测试。
* */
static final int tableSizeFor(int cap) {
    //>>> : 无符号右移,忽略符号位,空位都以0补齐,即这里保证数组长度为正数
    //>> :带符号右移,正数右移高位补0,负数右移高位补1
    
    //以下运算为何可以保证返回值为2的n次方呢?
    //我们随便拿个数字cap=190,那么n = 189,二进制为:10111101
    //10111101 |= 01011110 结果为: 11111111 = 2的8次方-1
    //接着后面的|=已经无需在意,因此这里189的二进制所有位都为1了
    //最后返回值为n+1即2的8次方 = 256
    //经上面例子得出结果:通过将cap-1的二进制最高位1后所有位都转为1,最后再加上1就是我们想要的2的n次方了
    
    //这里再详细说一下下面算法:
    int n = cap - 1;
        n |= n >>> 1; //将最高位后1位 置为1
        n |= n >>> 2; //将最高位后2位 置为1
        n |= n >>> 4; //由于已经将前4位置为1了,这时n>>>4用于转后4位
        n |= n >>> 8; //由于已经将前8位置为1了,这时n>>>8用于转后8位
        n |= n >>> 16; //由于已经将前16位置为1了,这时n>>>16用于转后16位
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

//根据指定map构造一个新的HashMap,默认加载因子为0.75
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}

//将map内容拷贝到本地HashMap,关于evict参数,HashMap中用于判断数组是否处于创建模式
//evict = false的话,处于创建模式
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size(); //获取map大小
    if (s > 0) {  //map 非空
        if (table == null) { //若当前数组为空,根据map大小获取数组阈值
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        else if (s > threshold) //否则当前数组为空且map大小比阈值大,需要扩容才能放得下map
            resize(); //关于扩容函数,后面会分析
        //处理好数组号,将map的所有值都拷贝到HashMap
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}

put相关函数

//对外接口,put一个key-value
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

//根据传递的key对象获取hash值
static final int hash(Object key) {
    //我们上面说过,要尽可能减少碰撞(不同key在数组中处于相同位置),保证均匀性以满足散列特征。
    int h;
    //key的hash值的高16位不变,低16位与高16位异或作为key的最终hash值
    //我们前面知道数组长度总是为2的n次方,并且最后根据key的hash值取数组下标的时候公式为:
    //int index = (table.length - 1) & hash;
    //假如key.hashCode() = 1111-0001-1111-1111-0111-1110-1111-1000,数组长度为2⁴=16=0001-0110
    //最高的24位都没有参与运算,因为数组长度的限制(我们事实也很难会用到2^30这个最大容量)。因此,
    //大多数情况下hash值的高位都没有参与运算(例子这里只有低5位参与运算),这样的话很容易产生碰撞,
    //因此设计者通过将高16位与低16位异或来减少这种影响
    //更详细说明可参考:https://www.cnblogs.com/zhengwang/p/8136164.html
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}


//内部实现类,put一个key-value
//hash:key的哈希值
//key: key值
//value: value值
//onlyIfAbsent: true的话,不改变原本已经存在的key-value
//evict: 关于evict参数,evict = false的话,table处于创建模式
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    
     //若数组为空或者数组长度为0则扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length; 
    
    //根据hash获取数组中的下标,若数组中不存在节点的话,new一个节点并放到对应数组下标
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    
    //若数组中已经存在节点,当前节点为tab[(n - 1) & hash]
    else { 
        Node<K,V> e; K k;
        //若当前节点与要插入的key-value的key相等的话,将e赋值为当前节点p
        //这里的key相等为以下判断,即要插入的key-value的key的hash值与当前节点的hash值相等
        //且k=当前节点的key与key相等或要插入的key-value的key.equals(k)为true。
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        
        //若当前节点为红黑树节点的话,e=红黑树节点的putTreeVal方法的返回值
        //putTreeVal后面会细讲
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        
        //下面代码表示循环链表,找出链表与要插入的key-value的key相等的节点
        else {
            for (int binCount = 0; ; ++binCount) {
                //若在链表中没有找到对应key(最后节点.next为null)的节点,跳出循环
                if ((e = p.next) == null) {
                    //new一个节点,并插到链表最后面
                    p.next = newNode(hash, key, value, null);
                    
                    //判断当前链表长度是否比红黑树定义的阈值大,是的话,则开始将链表节点转化为红黑树节点
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                
                //若在链表中找到了与要插入的key-value的key相等的节点,跳出循环
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                
                //将next赋给当前节点,用于循环
                p = e;
            }
        }
        
        //在链表中找到了与要插入的key-value的key相等的节点,例如,相同一个key就会有相同的节点
        if (e != null) { 
            V oldValue = e.value;
            //判断onlyIfAbsent=false或原本key-value的值为空的时候则赋为最新值
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e); //LinkedHashMap后期操作函数,HashMap暂未实现
            return oldValue; //返回旧的值
        }
    }
    
    ++modCount; //增加修改次数
    
    //判断是否需要扩容
    if (++size > threshold)
        resize();
    
    afterNodeInsertion(evict);//LinkedHashMap后期操作函数,HashMap暂未实现
    return null;
}

//将链表节点转化为红黑树节点
//tab: 链表节点数组
//hash: 当前要插入key的hash值
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    
    //若tab为null或数组长度小于红黑树的最小容量的话,没有必要将链表转为红黑树,直接扩容
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    
    //e = tab[index = (n - 1) & hash]) 取数组中的节点即链表中的第一个节点
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);//将链表节点转为红黑树节点
            if (tl == null)
                hd = p; //记录红黑树头节点即链表中第一个节点
            else {
                //新增一个双向链表
                p.prev = tl; //设置当前节点的前一个节点
                tl.next = p; //设置上一个节点的后一个节点
            }
            tl = p; //将上一个节点赋值为当前节点,用于循环,直到链表最后一个
        } while ((e = e.next) != null);
        
        //若头节点非空,则从红黑树头节点开始将链表转化为红黑树结构
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

//从当前节点开始将链表节点转化为红黑树数据结构
final void treeify(Node<K,V>[] tab) {
    TreeNode<K,V> root = null; //根节点
    //从当前节点开始,进行结构转换
    //x为当前链表节点
    for (TreeNode<K,V> x = this, next; x != null; x = next) {
        //获取当前节点的后一个节点
        next = (TreeNode<K,V>)x.next;
        x.left = x.right = null; //设置左右节点为null
        
        //若根节点为空,则将当前节点设置为根节点
        //并且parent设置为null,因为根节点没有父节点
        //并且根节点一定为黑色
        if (root == null) {
            x.parent = null;
            x.red = false;
            root = x;
        }
        
        //若根节点非空
        else {
            K k = x.key; //当前链表节点的key
            int h = x.hash; //当前链表节点的hash值
            Class<?> kc = null; //声明key的class
            
            //从根节点开始进行构成红黑树结构
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; //声明方向,当前节点hash值
                K pk = p.key; //声明当前节点的key
                
                //若当前红黑树节点的hash比链表节点的hash值大的话,则将链表节点放在红黑树节点的左边
                if ((ph = p.hash) > h)
                    dir = -1; 
                //若当前红黑树节点的hash比链表节点的hash值小的话,则将链表节点放在红黑树节点的右边
                else if (ph < h)
                    dir = 1;
                
                //若当前红黑树节点的hash与链表节点的hash值相等的话需要再进行判断(即判断两个key是否相同)
                //若kc为null并且当前链表节点的key的class没有实现comparable接口
                //或者kc不为null并且当前链表节点的key的class实现了comparable,则通过比较得出方向
                //否则将调用tieBreakOrder(k, pk)获取一个必定为左或右的方向
                else if ((kc == null && (kc = comparableClassFor(k)) == null) 
                         || (dir = compareComparables(kc, k, pk)) == 0)
                    dir = tieBreakOrder(k, pk);

                //声明xp=当前红黑树节点
                TreeNode<K,V> xp = p;
                //根据方向获取当前红黑树节点的左节点或右节点
                //若为空的话,则将当前链表节点x插入到当前红黑树节点的左边或右边并且退出循环
                //否则继续循环
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    x.parent = xp; //设置当前链表节点的父节点为当前红黑树节点
                    
                    if (dir <= 0) 
                        xp.left = x; //当前链表节点x为当前红黑树节点的左节点
                    else
                        xp.right = x; //当前链表节点x为当前红黑树节点的右节点
                    
                    //插入后需要调整红黑树平衡,并且将平衡后返回的节点作为根节点
                    root = balanceInsertion(root, x); 
                    break;
                }
            }
        }
    }
    
    moveRootToFront(tab, root);
}

//根据对象获取实现Comparable的接口类
static Class<?> comparableClassFor(Object x) {
    //implement Comparable接口
    if (x instanceof Comparable) {
        Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
        
        //若class为String.class的话则直接返回,因为String已实现Comparable<String>接口
        if ((c = x.getClass()) == String.class) // bypass checks
            return c;
        
        //获取class实现的接口
        if ((ts = c.getGenericInterfaces()) != null) {
            for (int i = 0; i < ts.length; ++i) {
                //遍历所有接口
                //判断是否继承Comparable接口
                if (((t = ts[i]) instanceof ParameterizedType) &&
                    ((p = (ParameterizedType)t).getRawType() ==
                     Comparable.class) &&
                    (as = p.getActualTypeArguments()) != null &&
                    as.length == 1 && as[0] == c) 
                    //是的话直接返回class
                    return c;
            }
        }
    }
    return null;
}

//根据class比较两个对象
static int compareComparables(Class<?> kc, Object k, Object x) {
    return (x == null || x.getClass() != kc ? 0 :
            ((Comparable)k).compareTo(x));
}

//该方法一定会返回一个要么为1,要么为-1的值
//若a为null, 或者b为null,或者a的class name和b的class name相等
//则直接返回a和b的System.identityHashCode值相比较,若还是一样的话,则返回-1
//否则返回1
static int tieBreakOrder(Object a, Object b) {
    int d;
    if (a == null || b == null ||
        (d = a.getClass().getName().
         compareTo(b.getClass().getName())) == 0)
        d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
             -1 : 1);
    return d;
}

//插入一个key-value后需要重新调整红黑树结构,使其达到平衡,即保证log(n)的时间复杂度
//root: 根节点
//x:要插入的红黑树节点
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                            TreeNode<K,V> x) {
    x.red = true; //红黑树规则:新插入的节点颜色总是红色的
    
    //声明
    //xp: 要插入节点的父节点
    //xpp: 要插入节点的爷爷节点
    //xppl: 要插入节点的爷爷节点的左节点
    //xppr:要插入节点的爷爷节点的右节点
    for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
        //若父节点为null,则当前要插入的节点x为根节点
        //将根节点置为黑色,返回当前要插入的节点x
        if ((xp = x.parent) == null) {
            x.red = false;
            return x;
        }
        
        //若父节点为黑色或者父节点的父节点为null的话,返回根节点
        //因为当前父节点即为根节点,且x节点为红色,没有违反红黑树规则
        else if (!xp.red || (xpp = xp.parent) == null)
            return root;
        
        //往下执行: 父节点为红色 
        //若父节点为爷爷节点的左节点
        if (xp == (xppl = xpp.left)) {
            //爷爷节点的右节点不等于null且为红色
            //满足红黑树插入平衡情况一: 插入节点的父节点和其叔叔节点(爷爷节点的另一个子节点)均为红色
            //则需要执行变色操作
            if ((xppr = xpp.right) != null && xppr.red) {
                xppr.red = false; //将爷爷节点的右节点置为黑色
                xp.red = false; //将父节点置为黑色
                xpp.red = true; //将爷爷节点置为红色
                x = xpp; //将当前节点赋值为爷爷节点,再进行平衡操作
            }
            else {
                //若当前节点为父节点的右节点
                //满足红黑树插入平衡情况二: 插入节点的父节点是红色的,叔叔节点(爷爷节点的另一个子节点)是黑               //色的且插入节点是其父节点的右子节点。
                //由于当前父节点为爷爷节点的左节点,因此这里需要将当前节点的父节点作为新的节点,
                //以新的当前节点为支点做左旋操作
                if (x == xp.right) {
                    //将当前节点设置为父节点且执行左旋操作
                    root = rotateLeft(root, x = xp);
                    //重新设置爷爷节点和父节点
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                
                //若父节点不为空将其设置为黑色
                //满足红黑树插入平衡情况三: 插入节点的父节点是红色的,叔叔节点(爷爷节点的另一个子节点)                 //是黑色的,且插入节点是其父节点的左子节点。
                //由于当前父节点为爷爷节点的左节点,因此这里需要将当前节点的父节点涂黑,
                //爷爷节点涂红,以爷爷节点为支点做右旋操作
                if (xp != null) {
                    xp.red = false; 
                    
                    //若爷爷节点不为空,将其设置为红色并且执行右旋操作
                    if (xpp != null) {
                        xpp.red = true;
                        root = rotateRight(root, xpp);
                    }
                }
            }
        }
        //若父节点为爷爷节点的右节点
        else {
            //若要插入节点的爷爷节点的左节点非空且为红色
            //满足红黑树插入平衡情况一: 插入节点的父节点和其叔叔节点(爷爷节点的另一个子节点)均为红色
            //则需要执行变色操作
            if (xppl != null && xppl.red) {
                xppl.red = false; //将要插入节点的爷爷节点的左节点设置为黑色
                xp.red = false; //将父节点设置为黑色
                xpp.red = true; //将爷爷节点设置为红色
                x = xpp; //将当前节点赋值为爷爷节点,再进行平衡操作
            }
            //若要插入节点的爷爷节点的左节点为空或者为黑色
            else {
                //若当前节点为父节点的左节点
                //满足红黑树插入平衡情况二: 插入节点的父节点是红色的,叔叔节点(爷爷节点的另一个子节点)是黑               //色的且插入节点是其父节点的右子节点。
                //由于当前父节点为爷爷节点的右节点,因此这里需要将当前节点的父节点作为新的节点,
                //以新的当前节点为支点做右旋操作
                if (x == xp.left) {
                    root = rotateRight(root, x = xp); //将当前节点设置为父节点且执行右旋操作
                    
                    //重新设置爷爷节点和父节点
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                
                //若父节点不为空将其设置为黑色
                //满足红黑树插入平衡情况三: 插入节点的父节点是红色的,叔叔节点(爷爷节点的另一个子节点)                 //是黑色的,且插入节点是其父节点的左子节点。
                //由于当前父节点为爷爷节点的右节点,因此这里需要将当前节点的父节点涂黑,
                //爷爷节点涂红,以爷爷节点为支点做左旋操作
                if (xp != null) {
                    xp.red = false; 
                    
                    //若爷爷节点非空
                    if (xpp != null) {
                        xpp.red = true; //爷爷节点设置为红色
                        root = rotateLeft(root, xpp); //将爷爷节点执行左旋操作
                    }
                }
            }
        }
    }
}

//左旋操作
//root: 红黑树根节点
//p:当前节点
/*
* 左旋示意图:对节点x进行左旋
*       p                        p
*      /                        /
*     x                        y
*   /   \                    /   \
* lx     y      ----->      x    ry
*      /   \              /   \
*    ly      ry          lx    ly
**/
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                      TreeNode<K,V> p) {
    //声明r: 当前节点的右节点
    //pp:当前节点的父节点
    //rl: 当前节点右节点的左节点
    TreeNode<K,V> r, pp, rl; 
    
    //若当前节点不为null且当前节点的右节点不为空
    if (p != null && (r = p.right) != null) {
        //将当前节点右节点设置为当前节点右节点的左节点
        if ((rl = p.right = r.left) != null)
            rl.parent = p;
        
        //将当前节点右节点的父节点设置为当前节点的父节点
        //若父节点为null,则设置当前节点的右节点为根节点,同时设置为黑色
        if ((pp = r.parent = p.parent) == null)
            (root = r).red = false;
        
        //当前节点父节点非null且当前节点为父节点的左节点
        else if (pp.left == p)
            //父节点的左节点设置为当前节点的右节点
            pp.left = r;
        
        //当前节点为父节点的右节点
        else
            //将父节点的右节点设置为当前节点的右节点
            pp.right = r;
        
        //将当前节点设置为右节点的左节点
        r.left = p;
        
        //将当前节点的父节点设置为右节点
        p.parent = r;
    }
    return root;
}

//右旋操作
//root: 红黑树根节点
//p:当前节点
/*
 * 右转示意图:对节点y进行右旋
 *         p                    p
 *        /                    /
 *       y                    x
 *     /   \                /   \
 *    x     ry   ----->    lx    y
 *  /   \                      /   \
 * lx    rx                   rx   ry
 */
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                       TreeNode<K,V> p) {
    //声明l: 当前节点的左节点
    //pp:当前节点的父节点
    //lr: 当前节点的左节点的右节点
    TreeNode<K,V> l, pp, lr;
    
    //当前节点非null且左节点非null
    if (p != null && (l = p.left) != null) {
        //若当前节点的左节点的右节点非null,则当前节点的左节点的右节点的父节点为当前节点
        if ((lr = p.left = l.right) != null)
            lr.parent = p;
        
        //将当前节点左节点的父节点设置为当前节点的父节点
        //若父节点为null,则设置当前节点的左节点为根节点,同时设置为黑色
        if ((pp = l.parent = p.parent) == null)
            (root = l).red = false;
        
        //当前节点父节点非null且当前节点为父节点的右节点
        else if (pp.right == p)
            //父节点的右节点设置为当前节点的左节点
            pp.right = l;
        
        //当前节点为父节点的左节点
        else
            //将父节点的左节点设置为当前节点的左节点
            pp.left = l;
        
        //设置左节点的右节点为当前节点
        l.right = p;
        
        //设置当前节点的父节点为左节点
        p.parent = l;
    }
    return root;
}

//用于确保红黑树中的根节点为数组中的下标节点(头节点)
//tab: 节点数组
//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]; //获取数组下标中的节点(头节点)
        
        if (root != first) { //若红黑树的根节点不是头节点的话
            Node<K,V> rn; //声明红黑树根节点的后一个节点
            tab[index] = root; //设置数组下标的节点(头节点)为红黑树根节点
            TreeNode<K,V> rp = root.prev; //声明红黑树根节点的链表的前一个节点rp
            
            if ((rn = root.next) != null) //如果红黑树根节点的后一个节点不为空的话
                //设置红黑树根节点的下一个节点的前一个节点为根节点的前一个节点
                ((TreeNode<K,V>)rn).prev = rp; 
            
            
            if (rp != null) //若根节点的前一个节点不为空 
                rp.next = rn; //将根节点的前一个节点设置根节点的后一个节点
            
            
            //若头节点不为null
            if (first != null) 
                first.prev = root; //将头结点的前一个节点设置为红黑树根节点
            
            root.next = first; //红黑树根节点的后一个节点设置为头结点
            root.prev = null;
        }
        //检查红黑树是否正常
        assert checkInvariants(root);
    }
}

//递归检查红黑树是否正常

//红黑树基本规则:
//1.每个节点不是红色就是黑色的;
//2.根节点总是黑色的;
//3.如果节点是红色的,则它的子节点必须是黑色的(反之不一定),(也就是从每个叶子到根的所有路径上不能有两个连续的红色节点);
static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
    //声明父节点,左节点,右节点,前节点,后节点
    TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
    tb = t.prev, tn = (TreeNode<K,V>)t.next;
    
    //若前节点不为null且前节点的后节点不等于当前节点,证明和链表中位置不一致,异常
    if (tb != null && tb.next != t)
        return false;
    
    //若后节点不为null且后节点的前节点不等于当前节点,证明和链表中位置不一致,异常
    if (tn != null && tn.prev != t)
        return false;
    
    //若父节点不为null,且当前节点不是父节点的左节点又不是父节点的右节点,异常
    if (tp != null && t != tp.left && t != tp.right)
        return false;
    
    //若当前左节点不为null且左节点的父节点不等于当前节点或者左节点的hash值比当前节点的hash值大的话,异常
    if (tl != null && (tl.parent != t || tl.hash > t.hash))
        return false;
    
    //若当前右节点不为null且右节点的父节点不等于当前节点或右节点的hash值比当前节点的hash值小的话,异常
    if (tr != null && (tr.parent != t || tr.hash < t.hash))
        return false;
    
    //若当前节点为红色,且左节点不为空且为红色,且右节点不为空且为红色,异常
    if (t.red && tl != null && tl.red && tr != null && tr.red)
        return false;
    
    //若左节点不为nul则递归判断左节点
    if (tl != null && !checkInvariants(tl))
        return false;
    
     //若左节点不为nul则递归判断右节点
    if (tr != null && !checkInvariants(tr))
        return false;
    
    //红黑树结构正常,返回true
    return true;
}

//红黑树的put操作
//map: 要put的hash map
//tab: 数组
//h: 要put的key-value的key的hash值
//k: 要put的key-value的key
//v: 要put的key-value的value
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                               int h, K k, V v) {
    Class<?> kc = null; //声明key的class临时变量
    boolean searched = false; //声明是否搜索过的bool临时变量
    TreeNode<K,V> root = (parent != null) ? root() : this; //获取根节点
    
    //p为当前节点,从当前红黑树的根节点遍历,找到要put的kay-value的key的对应节点或者
    //得到新节点在红黑树的位置
    for (TreeNode<K,V> p = root;;) { 
        int dir, ph; K pk; //声明方向,当前节点的hash值,当前节点的key
        
        //若要put的key-value的key的hash值比当前节点hash值小
        if ((ph = p.hash) > h)
            dir = -1; //在当前节点左边
        
        //若要put的key-value的key的hash值比当前节点hash值大
        else if (ph < h)
            dir = 1; //在当前节点右边
        
        //若要put的key-value的key与当前红黑树节点的key相等,直接返回当前节点
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))
            return p;
        
        //若还没得到方向的话,下面代码保证得到一个方向值
        else if ((kc == null &&
                  //若key的class实现了Comparable接口的话,直接比较两个key,得到方向
                  (kc = comparableClassFor(k)) == null) || 
                 (dir = compareComparables(kc, k, pk)) == 0) {
            //判断是否搜索过
            //该方法代表在put的时候会从红黑树中查找对应的key是否存在,若存在的话,
            //直接返回在红黑树中找到的节点。否则会将put的key-value封装成一个红黑树节点
            //插入到红黑树中
            if (!searched) {
                TreeNode<K,V> q, ch; //声明查找到的节点q和临时节点遍历ch
                searched = true; //将搜索标识置为true
                
                //若当前节点的左节点不为null并且从左节点开始查找节点,查找到的节点不为空的话
                //返回查找到的节点
                if (((ch = p.left) != null &&
                     (q = ch.find(h, k, kc)) != null) 
                    
                    ||
                    
                    //或
                   //若当前节点的右节点不为null并且从右节点开始查找节点,查找到的节点不为空的话
                    //返回查找到的节点
                    ((ch = p.right) != null &&
                     (q = ch.find(h, k, kc)) != null))
                    return q;
            }
            
            //该方法一定会返回一个要么为1,要么为-1的值(即一定会得到一个左/右的方向)
            dir = tieBreakOrder(k, pk);
        }

        //程序执行到这里证明要新创建一个红黑树节点,并将其插入的红黑树中
        
        TreeNode<K,V> xp = p;  //声明父节点
        //判断要插入的方向
        if ((p = (dir <= 0) ? p.left : p.right) == null) {
            Node<K,V> xpn = xp.next; //声明父节点的后一个节点
            
            //根据key的hash值、key、value、父节点的后一个节点创建一个红黑树节点
            TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn); 
            
            //新节点在父节点的位置
            //即为父节点的左节点或右节点
            if (dir <= 0)
                xp.left = x;
            else
                xp.right = x;
            
            //父节点的后一个节点为新节点
            xp.next = x;
            
            //新节点的父节点和新节点的前节点都为父节点
            x.parent = x.prev = xp;
            
            //如果父节点的后一个节点不为null的话,将父节点的后一个节点的前一个节点设置为新节点
            if (xpn != null)
                ((TreeNode<K,V>)xpn).prev = x;
            
            //重新平衡红黑树后检查树结构再移动根节点为数组下标节点(头节点)
            moveRootToFront(tab, balanceInsertion(root, x));
            return null;
        }
    }
}

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

//根据key的hash值,key,key的class,从红黑树的当前节点开始,找到对应的节点
//h: key的hash值
//k: key
//kc: key的class
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
    TreeNode<K,V> p = this; //赋值当前节点
    
    do {
        int ph, dir; K pk; //声明当前key的hash值,方向,key
        TreeNode<K,V> pl = p.left, pr = p.right, q; //声明当前节点的左节点,右节点,q为查找节点
        
        if ((ph = p.hash) > h)
            p = pl; //如果当前节点的hash值大于key的hash值,则将当前节点赋值为左节点继续查找
        
        else if (ph < h)
            p = pr; //如果当前节点的hash值小于key的hash值,则将当前节点赋值为右节点继续查找
        
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))
            return p; //如果hash值等于key的hash值,返回当前节点,证明找到了
        
        else if (pl == null) //如果左节点为null,则将当前节点当前赋值为右节点继续查找
            p = pr;
        
        else if (pr == null) //如果右节点为null,则将当前节点当前赋值为左节点继续查找
            p = pl;
        
        //若key的class实现了Comparable接口的话,通过Comparable接口比较两个key的大小
        //将当前节点赋值为对应的方向的节点
        else if ((kc != null ||
                  (kc = comparableClassFor(k)) != null) &&
                 (dir = compareComparables(kc, k, pk)) != 0)
            p = (dir < 0) ? pl : pr;
        
        //若在上面接口得到kc后,这里通过find再寻找一次
        //若不为null,证明已找到对应key的节点,返回
        else if ((q = pr.find(h, k, kc)) != null)
            return q;
        
        //否当前节点直接赋值为左节点再继续查找
        else
            p = pl;
        
    } while (p != null); //若当前节点不为null,继续查找
    
    return null;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345