JDK 1.8 HashMap 源码分析(二)

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

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

主要从以下功能切入:

  • 构造函数
  • put
  • get
  • remove

get相关函数

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

//判断是否包含某个key
public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

//获取节点
//hash: key的hash值
//key: key
final Node<K,V> getNode(int hash, Object key) {
    //声明tab: 当前节点数组
    //first: 头节点
    //n: 数组长度
    //k: key
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k; 
    
    //若数组不为空切数组长度大于0,切头节点不为null的话则继续执行
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        
        //判断key是否与头节点匹配
        if (first.hash == hash && 
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        
        //若头结点的后一个节点非null,值继续执行
        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); //若头结点的后一个节点非null,值继续执行
        }
    }
    return null;
}


//从根节点开始查找对应key的红黑树节点
final TreeNode<K,V> getTreeNode(int h, Object k) {
    return ((parent != null) ? root() : this).find(h, k, null);
}

remove相关函数

//移除对应key的节点
//若存在对应节点,返回对应的值,否则返回null
public V remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}

//移除节点
//hash: key的hash值
//key: 对应key
//value: 对应value
//matchValue: 如果为true,只有value相等时才移除
//movable: 如果为false,则在移除的时候不移动其他节点
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    //声明数组tab,当前节点p,数组长度n,对应hash的数组下标index
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    
    //只有在数组不为null,数组长度大于0,对应数组下标的节点不为null才继续执行
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        
        //声明要返回的即要删除节点变量node、当前节点的下一个节点e,当前节点的key k,当前节点的value v
        Node<K,V> node = null, e; K k; V v;
        
        //如果当前节点的key与要删除的key相等(这里的相等为满足以下条件),则将node赋值为当前节点
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        //key不相等,且当前节点的下一个节点不为null
        else if ((e = p.next) != null) {
            //如果当前节点为红黑树节点,则通过红黑树查找对应的key的红黑树节点
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            
            //不是红黑树节点,即为链表节点
            else {
                do {
                    //循环链表,找到链表中与要删除key相等的节点
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        
        //要删除的节点不为null且
        //matchValue = false 或者要删除的节点value等于要删除的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)
                //对应数组下标的头节点为要删除节点的下一个节点
                tab[index] = node.next;
            //如果要删除的节点不是数组中的头节点的话
            else
                //当前节点为要删除的节点的下一个节点
                p.next = node.next;
            
            ++modCount; //修改次数+1
            --size; //实际数量-1
            afterNodeRemoval(node); //LinkedHashMap后期操作函数,HashMap暂未实现
            return node;
        }
    }
    return null;
}


//红黑树的节点移除方法,移除给定的节点,即哪个节点调用该方法,就删除哪个节点
//map: hashmap对象
//tab: 数组对象
//movable: 如果为false,则在移除的时候不移动其他节点 
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
                          boolean movable) {
    //声明数组长度
    int n; 
    //如果数组为null或数组长度为0,返回
    if (tab == null || (n = tab.length) == 0)
        return;
    
    //获取当前要删除的节点(下面一律为当前节点)在数组中的下标
    int index = (n - 1) & hash;
    
    //声明数组中头节点first,红黑树中的根节点root,两者相等
    //根节点的左节点rl
    TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
    
    //声明当前节点的下一个节点succ
    //当前节点的前一个节点prev
    TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
    
    //若当前节点的前一个节点为null,则头节点为当前节点的下一个节点
    if (pred == null)
        tab[index] = first = succ;
    
    //若当前节点的前一个节点不为null
    //则将当前节点的前一个节点的下一个节点赋值为当前节点的前一个节点
    else
        pred.next = succ;
    
    //若当前节点的下一个节点不为null
    if (succ != null)
        //则将当前节点的下一个节点赋值为当前节点的前一个节点
        succ.prev = pred;
    
    //若头节点为null,返回
    if (first == null)
        return;
    
    //如果根节点的父节点不为null的话
    if (root.parent != null)
        //重新获取真正的根节点
        root = root.root();
    
    //如果根节点为null,或者根节点的右节点为null
    //或者根节点的左节点为null,或者根节点的左节点的左节点为null
    if (root == null || root.right == null ||
        (rl = root.left) == null || rl.left == null) {
        //红黑树节点数量太小,重新转化为链表结构后返回,这种情况可以参考下面情况
        //    p            
        //      \
        //        p1
        //          \
        //            p2
        //             \
        //              p3
        //                \
        //                  p4
        //                    \
        //                    .....
        //因为这时时间红黑树的时间复杂度约等于链表的时间复杂度O(n)
        tab[index] = first.untreeify(map);  
        return;
    }
    
    //下面这部分内容,建议手动画一次,更加容易理解
    //我们知道,对于一棵普通的二叉排序树来说,删除的节点情况可以分为3种:
    //1. 叶子节点(即没有左右子树)
    //2. 只有左子树或只有右子树的节点
    //3. 既有左子树又有右子树的节点
     
    //this为当前节点,也是要删除的节点
    //声明当前节点,当前节点的左节点,当前节点的右节点,代替节点
    TreeNode<K,V> p = this, pl = left, pr = right, replacement;
    
    //若左节点和右节点都不为null,为情况3:
    //删除既有左子树又有右子树的节点,我们首先要找到该节点的后继节点,
    //后继节点需要继承待删除节点的父亲,孩子和颜色,然后替换待删除节点。
    if (pl != null && pr != null) {
        //声明后继节点s,后继节点的左节点sl
        TreeNode<K,V> s = pr, sl;
       
        //寻找后继节点:右节点的最左节点(因为最小的后继节点也比待删除节点的左节点大)
        //还有一种方法是寻找前驱节点,可自行百度
        while ((sl = s.left) != null) 
            s = sl;
        
        //交换颜色,交换当前节点的颜色和后继节点的颜色
        boolean c = s.red; s.red = p.red; p.red = c; 
        
        //声明后继节点的右节点
        TreeNode<K,V> sr = s.right;
        
        //声明当前节点的父节点
        TreeNode<K,V> pp = p.parent;
        
        //后继节点就是待删除节点的右节点,那么直接将后继节点代替待删除节点即可
        if (s == pr) { 
            //当前节点的父节点赋值为后继节点
            p.parent = s;
            //后继节点的右节点赋值为当前节点
            s.right = p;
        }
        
        //后继节点不是待删除节点的右节点
        else {
            //声明后继节点的父节点sp
            TreeNode<K,V> sp = s.parent;
            
            //首先将当前节点的父节点赋值为后继节点的父节点
            //若后继节点的父节点不为null继续执行函数
            if ((p.parent = sp) != null) {
                //如果后继节点在其父节点的左边
                if (s == sp.left)
                    //将后继节点的父节点的左节点赋值为当前节点
                    sp.left = p;
                
                //如果后继节点在其父节点的右边
                else
                    //将后继节点的父节点的右节点赋值为当前节点
                    sp.right = p;
            }

            //首先将后继节点的右节点赋值为当前节点的右节点
            if ((s.right = pr) != null)
                //若果当前节点的右节点不为null
                //将右节点的父节点赋值为后继节点
                pr.parent = s;
        }
        
        
        //-----------------------------
        //下面为一系列的代替关系操作
        //-----------------------------
        
        //将当前节点的左节点置为null
        p.left = null;
        
        //首先将当前节点的父节点赋值为后继节点的右节点
        if ((p.right = sr) != null)
            //如果后继节点的右节点不为null
            //将后继节点的右节点的父节点赋值为当前节点
            sr.parent = p;
        
        //首先将后继节点的左节点赋值为当前节点的左节点
        if ((s.left = pl) != null)
            //如果当前节点的左节点不为null
            //将当前节点的左节点的父节点赋值为后继节点
            pl.parent = s;
        
        //首先将后继节点的父节点赋值为当前节点的父节点
        if ((s.parent = pp) == null)
            //若当前节点的父节点为null
            //则将根节点赋值为后继节点
            root = s;
        
        //若当前节点的父节点不为null
        //且当前节点在其父节点左边
        else if (p == pp.left)
            //将当前节点的父节点的左节点赋值为后继节点
            pp.left = s;
        //若当前节点的父节点不为null
        //且当前节点在其父节点右边
        else
            //将当前节点的父节点的右节点赋值为后继节点
            pp.right = s;
        
        //若后继节点的右节点不为null
        
        if (sr != null)
            //代替节点为后继节点的右节点
            replacement = sr;
        
        //若后继节点的右节点为null
        else
            //代替节点为当前节点
            replacement = p;
    }
    
    //左节点不为null,右节点为null
    else if (pl != null)
        //代替节点为左节点
        replacement = pl;
    
    //左节点为null,右节点不为null
    else if (pr != null)
        //代替节点为右节点
        replacement = pr;
    
    //左右节点都为null
    else
        //代替节点为当前节点
        replacement = p;
    
    //若代替节点不是当前节点,将要删除节点的相关节点赋值给代替节点
    if (replacement != p) {
        //声明当前节点的父节点pp
        //并且将代替节点的父节点赋值为当前节点的父节点
        TreeNode<K,V> pp = replacement.parent = p.parent;
        
        //如果当前节点的父节点为null的话
        //则将根节点赋值为代替节点
        if (pp == null)
            root = replacement;
        
        //如果当前节点的父节点不为null的话
        //且当前节点是其父节点的左节点
        else if (p == pp.left)
            //将当前节点的父节点的左节点赋值为代替节点
            pp.left = replacement;
        
        //如果当前节点的父节点不为null的话
        //且当前节点是其父节点的右节点
        else
            //将当前节点的父节点的右节点赋值为代替节点
            pp.right = replacement;
        
        //删除节点,将当前节点的左节点,右节点,父节点都置为null
        p.left = p.right = p.parent = null;
    }

    //若当前节点是红色的话删除不会破坏红黑树平衡(无需调整红黑树结构), r = 当前根节点
    //若当且节点是黑色的话,移除节点会破坏红黑树平衡,因此需要调用
    //balanceDeletion()方法使红黑树平衡,且r = 平衡之后的根节点
    TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);

    //如果代替节点等于当前要移除的节点的话,直接删除当前节点
    if (replacement == p) {  
        //声明当前节点的父节点
        TreeNode<K,V> pp = p.parent; 
        //将当前节点的父节点赋值为null
        p.parent = null;
        
        //若果当前节点的父节点不为null的话
        if (pp != null) {
            //如果当前节点在父节点左边,则将父节点的左节点赋值为null
            if (p == pp.left)
                pp.left = null;
            
            //如果当前节点在父节点右边,则将父节点的右节点赋值为null
            else if (p == pp.right)
                pp.right = null;
        }
    }
    
    //movable: 如果为true,则在移除的时候移动其他节点 
    if (movable)
        moveRootToFront(tab, r); //将根节点移动到数组下标中作为头节点
}

//红黑树经过节点删除后,需要重新调整红黑树结构,使之平衡
//root: 当前根节点
//x: 代替节点
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
                                           TreeNode<K,V> x) {
    //声明xp: 代替节点的父节点
    //xpl: 代替节点的父节点的左节点
    //xpr: 代替节点的父节点的右节点
    for (TreeNode<K,V> xp, xpl, xpr;;)  {
        
        //如果代替节点为null或者代替节点为根节点,无需平衡,直接返回
        if (x == null || x == root)
            return root;
        
        //如果代替节点不为null且代替节点不为根节点
        //且代替节点父节点为null,代替节点为根节点,置为黑色,返回代替节点
        else if ((xp = x.parent) == null) {
            x.red = false;
            return x;
        }
        
        //如果代替节点不为null且代替节点不为根节点且代替节点父节点不为null
        //且代替节点为红色,将代替节点置为黑色,无需调整,返回根节点
        else if (x.red) {
            x.red = false;
            return root;
        }
        
        //代替节点为黑色
        
        //x为父节点的左节点
        else if ((xpl = xp.left) == x) {
            //如果x有红色的兄弟节点xpr,那么它的父亲节点xp一定是黑色节点
            if ((xpr = xp.right) != null && xpr.red) {
                xpr.red = false; //将父节点的右节点置为黑色
                xp.red = true; //将父节点置为红色
                root = rotateLeft(root, xp); //对父节点进行左旋操作
                //重新将xp指向x的父节点,xpr指向xp新的右节点
                xpr = (xp = x.parent) == null ? null : xp.right; 
            }
            
            //如果xpr为null,将x的父节点xp作为新的x继续循环
            if (xpr == null)
                x = xp;
            else {
                //xpr不为null
                //声明sl为当前节点的父节点的右节点的左节点
                //声明sr为当前节点的父节点的右节点的右节点
                TreeNode<K,V> sl = xpr.left, sr = xpr.right;
                
                 //若sl和sr都为黑色或者都为null
                if ((sr == null || !sr.red) &&
                    (sl == null || !sl.red)) {
                   //xpr没有红色孩子,则将xpr置为红色
                    xpr.red = true;
                    //将x的父节点xp作为新的x继续循环
                    x = xp;
                }
                
                //若sl和sr不同为黑色或者两者不同时为null
                else {
                    //如果sr为null或者sr为黑色的话
                    if (sr == null || !sr.red) {
                        //如果sl不为null
                        if (sl != null)
                            //sl置为黑色
                            sl.red = false;
                        //xpr置为红色
                        xpr.red = true;
                        //将xpr进行右旋操作
                        root = rotateRight(root, xpr);
                        
                        //重新将xp指向x的父节点,xpr指向xp新的右节点
                        xpr = (xp = x.parent) == null ?
                            null : xp.right;
                    }
                    
                    //若xpr不为null
                    if (xpr != null) {
                        //如果xp为null的话则将xpr置为黑色,否则置为xp的颜色
                        xpr.red = (xp == null) ? false : xp.red;
                        
                        //sr不为null
                        if ((sr = xpr.right) != null)
                            //将sr置为黑色
                            sr.red = false;
                    }
                    
                    //如果xp即当前节点不为null
                    if (xp != null) {
                        //将xp置为黑色
                        xp.red = false;
                        //且将xp进行左旋操作,恢复平衡
                        root = rotateLeft(root, xp);
                    }
                    
                    //调整结束,下一次循环的时候会直接跳出
                    x = root;
                }
            }
        }
        
        //x为其父节点的右节点,跟上面类似
        else { 
            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;
                }
            }
        }
    }
}

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) { //若旧数组容量大于0且大于或等于最大容量,返回旧数组,因为容量已达最大数量
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        
        //若就新数组容量=旧数组容量/2,且旧数组容量大于初始化容量的话
        //新数组阈值=旧数组阈值/2
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; 
    }
    
    //旧数组的容量为0且旧数组阈值大于0的话,将新数组容量设置为旧数组阈值
    else if (oldThr > 0) 
        newCap = oldThr;
    //旧数组的容量为0且阈值为0
    else {              
        //新数组的容量为初始化容量
        newCap = DEFAULT_INITIAL_CAPACITY;
        //新数组的阈值为默认数值
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    
    //若新阈值大小还是为0的话,通过容量*负载因子赋值
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor; 
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    
    //将当前hashmap的阈值赋值为新的阈值
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    
    //根据新的容量大小创建新的数组
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    
    //将当前数组赋值为新的数组
    table = newTab;
    //若旧的数组不为null,将旧数组的值拷贝到新数组里面去
    if (oldTab != null) {
        //遍历数组
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            //若头节点不为null继续执行
            if ((e = oldTab[j]) != null) {
                
                oldTab[j] = null; //将旧数组的头节点清空
                
                //若头节点的下一个节点为null的话
                //直接将头节点放到新数组的对应下标中
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e; 
                
                //如果头节点为红黑树节点的话,则需要通过红黑树重新分布
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                
                //否则为链表方式处理,遍历链表中所有节点,重新放到对应的数组位置中
                else { 
                    //保证顺序
                    //这里使用 原索引下标 + 旧数组容量作为新索引下标,为什么这样做呢?
                    //详情看下面分析
                    
                    //声明 新索引下标=原索引下标 情况下的头节点和尾节点
                    Node<K,V> loHead = null, loTail = null;
                    
                    //声明 新索引下标=原索引下标+旧数组容量 情况下的头节点和尾节点
                    Node<K,V> hiHead = null, hiTail = null;
                    
                    //声明当前节点的下一个节点
                    Node<K,V> next;
                    do {
                        next = e.next; 
                        
                        //当前节点的hash值与旧的容量进行位的与运算,我们知道,容量大小始终为2的n次方,
                        //即容量大小的数值始终为1000***000(n个0)。
                        
                        //假设e的hash值为0011-1110-1111(随便指定的),
                        //旧数组容量oldCap为16=0001-0000,新数组容量newCap为32=0010-0000
                        //那么e.hash & oldcap = 0,证明hash的第5位为0
                        //我们知道原索引下标 = (数组长度 - 1) & hash,这里16 - 1 = 1111(二进制)
                        //那么新索引下标 = 32 - 1 = 0001-1111,新索引下标取决于hash的低5位,但是
                        //e.hash的第5位为0,那么原索引下标和新索引下标的计算实际上取的都是hash的低四位
                        //因此新索引下标=原索引下标 
                        if ((e.hash & oldCap) == 0) {
                            //指定头结点
                            if (loTail == null)
                                loHead = e;
                            //否则尾部节点的下一个节点赋值为当前节点
                            else
                                loTail.next = e;
                            //尾部节点赋值为当前节点
                            loTail = e;
                        }
                        
                        //假设e的hash值为0011-1111-1100(随便指定的),
                        //旧数组容量oldCap为16=0001-0000,新数组容量newCap为32=0010-0000
                        //那么e.hash & oldcap != 0,证明hash的第5位不等于0
                        //实际上,原索引下标 = hash & (16 - 1) = hash & 1111
                        //新索引下标 = hash & (32 - 1) = hash & 0001-1111
                        //新索引下标始终会比多1位1,即加上旧数组容量
                        //例如这里原索引下标 = 0011-1111-1100 & 1111 = 1100 = 12
                        //新索引下标 = 0011-1111-1100 & 11111 = 11100 = 28
                        //那么:12(原索引下标) + 16(旧数组容量) = 28(新索引下标)
                        else {
                            //指定头结点
                            if (hiTail == null)
                                hiHead = e;
                            else
                                //否则尾部节点的下一个节点赋值为当前节点
                                hiTail.next = e;
                             //尾部节点赋值为当前节点
                            hiTail = e;
                        }
                    } while ((e = next) != null); //将头节点的下一个节点不为null的话,循环执行
                    
                    //新索引下标=原索引下标
                    if (loTail != null) {
                        loTail.next = null; //尾节点的下一个节点赋值为null
                        newTab[j] = loHead; //头节点放到新数组下标中
                    }
                    
                    //新索引下标=原索引下标+旧数组容量
                    if (hiTail != null) {
                        hiTail.next = null; //尾节点的下一个节点赋值为null
                        newTab[j + oldCap] = hiHead; //头节点放到新数组下标中
                    }
                }
            }
        }
    }
    return newTab;
}

//红黑树重哈希
//map: hashmap对象
//tab: 新数组对象
//index: 旧数组下标
//bit: 旧数组容量
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
    TreeNode<K,V> b = this; //从当前节点开始
    
    //声明 新索引下标=原索引下标 情况下的头节点和尾节点
    TreeNode<K,V> loHead = null, loTail = null;
    
     //声明 新索引下标=原索引下标+旧数组容量 情况下的头节点和尾节点
    TreeNode<K,V> hiHead = null, hiTail = null;
    
    //声明lc: 新索引下标=原索引下标 情况下的节点数量
    //声明hc: 新索引下标=原索引下标+旧数组容量 情况下的节点数量
    int lc = 0, hc = 0;
    
    //遍历红黑树链表
    //这里的判断方法和resize方法里面链表的重hash类似
    //但是不同的是红黑树链表维护了一个双向链表
    //e:当前节点
    //next:当前节点的下一个节点
    for (TreeNode<K,V> e = b, next; e != null; e = next) {
        next = (TreeNode<K,V>)e.next;
        e.next = null;
        
        //新索引下标=原索引下标
        if ((e.hash & bit) == 0) {
            //指定头结点
            if ((e.prev = loTail) == null)
                loHead = e;
            else
                loTail.next = e;
            loTail = e;
            ++lc; //节点数量+1
        }
        
        //新索引下标=原索引下标+bit(旧数组容量)
        else {
            if ((e.prev = hiTail) == null)
                hiHead = e;
            else
                hiTail.next = e;
            hiTail = e;
            ++hc; //节点数量+1
        }
    }

    //新索引下标=原索引下标情况下
    //头节点不为null
    if (loHead != null) {
        // 节点个数少于等于6个的话则需要将红黑树转为链表结构
        if (lc <= UNTREEIFY_THRESHOLD)
            //新数组对应下标的节点为重新转为链表结构的头节点
            tab[index] = loHead.untreeify(map);
        else {
            //新数组对应下标的节点为红黑树链表的头节点
            tab[index] = loHead;
            
            //如果hiHead不为null的话,证明原来的树结构已经发生了改变,就是原本的树结构
            //一部分节点分给了loHead这个头节点带领的双向链表
            //另一部分节点分给了hiHead这个头节点带领的双向链表
            //这时需要以loHead为根节点, 构建新的红黑树
            if (hiHead != null) 
                loHead.treeify(tab);
        }
    }
    
    //新索引下标=原索引下标+bit(旧数组容量)情况下
    //头节点不为null
    if (hiHead != null) {
         // 节点个数少于等于6个的话则需要将红黑树转结构为链表结构
        if (hc <= UNTREEIFY_THRESHOLD)
            //新数组对应下标的节点为重新转为链表结构的头节点
            tab[index + bit] = hiHead.untreeify(map);
        else {
            //新数组对应下标的节点为红黑树链表的头节点
            tab[index + bit] = hiHead;
            
           //如果loHead不为null的话,证明原来的树结构已经发生了改变,就是原本的树结构
            //一部分节点分给了hiHead这个头节点带领的双向链表
            //另一部分节点分给了loHead这个头节点带领的双向链表
            //这时需要以loHead为根节点, 构建新的红黑树
            //而loHead节点在上面已经构建了新的红黑树了
            if (loHead != null)
                hiHead.treeify(tab);
        }
    }
}

//将红黑树转结构为链表结构
//双向链表->单向链表
final Node<K,V> untreeify(HashMap<K,V> map) {
    //声明头节点hd和记录上一个节点的t1
    Node<K,V> hd = null, tl = null;
    
    //遍历红黑树链表
    for (Node<K,V> q = this; q != null; q = q.next) {
        //红黑树节点转链表节点
        Node<K,V> p = map.replacementNode(q, null);
        
        //指定头节点
        if (tl == null)
            hd = p;
        //指定下一个节点
        else
            tl.next = p;
        
        tl = p;
    }
    
    //返回头节点
    return hd;
}

//红黑树节点转链表节点
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
    return new Node<>(p.hash, p.key, p.value, next);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342