HashMap源码之treeifyBin()方法

Posted heaven-elegy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap源码之treeifyBin()方法相关的知识,希望对你有一定的参考价值。

/**
* tree桶方法.
* 一般用作桶结构变更后,将桶中过长的链表转换为红黑树
*/
final void treeifyBin(Node<K,V>[] tab, int hash) {
    // n - 代表参数tab长度
    // index - tab中表示hash的下标
    // hash - 待处理的链表节点hash
    // e - 目标节点
    int n, index; Node<K,V> e;
    // 判断tab是否为空或tab长度MIN_TREEIFY_CAPACITY=64
    // 也就是说,在桶中单个链表长度可能已经达到要求(如putVal中的binCount >= TREEIFY_THRESHOLD - 1),但是桶容量未达标时,也不会进行tree化
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        // 表是空的或表容量小于MIN_TREEIFY_CAPACITY
        // 重置大小
        resize();
    // 可以tree化,检查链表节点是否存在
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        // 链表节点存在
        
        // 树节点头与尾
        TreeNode<K,V> hd = null, tl = null;
        // 已经有第一个目标,直接do while
        do {
            // 构造一个TreeNode.(这里没有额外逻辑,仅仅是使用当前的e创建了TreeNode)
            // 注意,这里的Tree继承自LinkedHashMap.Entry,内部包含了before与after的双向链表.但是TreeNode又自行实现了双向链表prev与next,并没有使用前者的数据结构
            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)
            // 调用TreeNode的treeify组装红黑树
            hd.treeify(tab);
    }
}

/**
* 组装红黑树
*/
final void treeify(Node<K,V>[] tab) {
    // 根节点(黑色节点)
    TreeNode<K,V> root = null;
    // 进行迭代.(当前this作用域位于TreeNode实例)
    // x表示当前遍历中的节点
    for (TreeNode<K,V> x = this, next; x != null; x = next) {
        // 缓存next
        next = (TreeNode<K,V>)x.next;
        // 保证当前节点左右节点为null
        x.left = x.right = null;
        // 判断是否存在根节点
        if (root == null) {
            // 不存在
            // 跟节点没有父级.所以设置为null
            x.parent = null;
            // 红黑树中,根节点是黑色的
            x.red = false;
            // 保存到局部变量
            root = x;
        }
        else {
            // 跟节点已确认
            
            // 缓存key
            K k = x.key;
            // 缓存hash
            int h = x.hash;
            // key类型
            Class<?> kc = null;
            // -------------------- 对跟节点进行遍历,查找插入位置 --------------------
            // p是插入节点的父节点
            for (TreeNode<K,V> p = root;;) {
                // dir - 用来表达左右.
                // ph - 父节点hash
                int dir, ph;
                // 父节点key
                K pk = p.key;
                
                // -------------------- 判断插入到左还是右节点 --------------------
                // 初始化父节点hash
                // 判断父节点hash是否大于当前节点hash
                if ((ph = p.hash) > h)
                    // dir = -1 插入节点在父节点左侧
                    dir = -1;
                // 判断父节点hash是否小于当前节点hash
                else if (ph < h)
                    // dir = 1 插入节点在父节点右侧
                    dir = 1;
                // 父节点hash等于当前节点hash,进行额外的处理
                // 这里使用了基于Class的一些处理办法,最终保证了dir的正确值(不为0) TODO 待补充 
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0)
                    dir = tieBreakOrder(k, pk);


                // -------------------- 获取左或右节点并进行操作 --------------------
                // 缓存插入节点的父节点
                TreeNode<K,V> xp = p;
                // 使用dir获取父节点对应的左或右节点,并且检查这个节点是否为null.不为null时,进入下一次循环
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    // 父节点左或右节点为null
                    
                    // 设置父级节点
                    x.parent = xp;
                    // 再次判断左右
                    if (dir <= 0)
                        // 将父节点的左子节点复制为当前节点
                        xp.left = x;
                    else
                        // 将父节点的右子节点复制为当前节点
                        xp.right = x;
                    // 进行平衡
                    root = balanceInsertion(root, x);
                    // 退出查找插入位置的循环,进行下一个元素的插入
                    break;
                }
            }
        }
    }
    
    // 因为在进行旋转操作时,可能会修改根节点到其他节点.导致桶中的直接节点为分支节点,所以需要进行修正
    moveRootToFront(tab, root);
}

以上是关于HashMap源码之treeifyBin()方法的主要内容,如果未能解决你的问题,请参考以下文章

一文解读JDK8中HashMap的源码

ConcurrentHashMap核心源码分析

Java中HashMap详解

Java中HashMap详解

HashMap系列之重要方法源码详解

HashMap源码之put()方法