美文网首页
jdk1.8的hashmap真的是大于8就转换成红黑树,小于6就

jdk1.8的hashmap真的是大于8就转换成红黑树,小于6就

作者: 挡不住的柳Willow | 来源:发表于2020-02-08 22:07 被阅读0次

    免责声明

    本文夹杂部分笔者个人观点,如描述有误,欢迎指正

    前言

    写这篇文章,是因为最近研究hashmap源码的时候,会结合网上的一些博客来促进理解。而关于红黑树和链表相互转换这一块,大部分的文章都会这样描述:hashmap中定义了两个常量:

    static final int TREEIFY_THRESHOLD = 8;
    static final int UNTREEIFY_THRESHOLD = 6;
    

    当链表元素个数大于8的时候,就会转换为红黑树;当红黑树元素个数小于6的时候,就会转换回链表。
    笔者通过仔细观察,发现这种说法并不严谨。hashMap中确实定义了这两个常量,但并非简单通过元素个数的判断来进行转换。

    链表转换为红黑树

    链表转换为红黑树的最终目的,是为了解决在map中元素过多,hash冲突较大,而导致的读写效率降低的问题。在源码的putVal方法中,有关红黑树结构化的分支为:

                    //此处遍历链表
                    for (int binCount = 0; ; ++binCount) {
                        //遍历到链表最后一个节点
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            //如果链表元素个数大于等于TREEIFY_THRESHOLD
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                //红黑树转换逻辑
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
    

    即网上所说的,链表的长度大于8的时候,就转换为红黑树,我们来看看treeifyBin方法:

    final void treeifyBin(Node<K,V>[] tab, int hash) {
            int n, index; Node<K,V> e;
        //先判断table的长度是否小于 MIN_TREEIFY_CAPACITY (64)
            if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        //小于64,则扩容
                resize();
            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);
            }
        }
    

    可以看到在treeifyBin中并不是简单地将链表转换为红黑树,而是先判断table的长度是否大于64,如果小于64,就通过扩容的方式来解决,避免红黑树结构化。原因呢?笔者个人觉得链表长度大于8有两种情况:

    • 1、table长度足够,hash冲突过多
    • 2、hash没有冲突,但是在计算table下标的时候,由于table长度太小,导致很多hash不一致的key计算的下标一致

    第二种情况是可以用扩容的方式来避免的,扩容后链表长度变短,读写效率自然提高。另外,扩容相对于转换为红黑树的好处在于可以保证数据结构更简单。
    由此可见并不是链表长度超过8就一定会转换成红黑树,而是先尝试扩容

    红黑树转换为链表

    基本思想是当红黑树中的元素减少并小于一定数量时,会切换回链表。而元素减少有两种情况:

    • 1、调用map的remove方法删除元素

    hashMap的remove方法,会进入到removeNode方法,找到要删除的节点,并判断node类型是否为treeNode,然后进入删除红黑树节点逻辑的removeTreeNode方法中,该方法有关解除红黑树结构的分支如下:

    //判断是否要解除红黑树的条件
    if (root == null || root.right == null ||
                    (rl = root.left) == null || rl.left == null) {
                    tab[index] = first.untreeify(map);  // too small
                    return;
                }
    

    可以看到,此处并没有利用到网上所说的,当节点数小于UNTREEIFY_THRESHOLD时才转换,而是通过红黑树根节点及其子节点是否为空来判断。而满足该条件的最大红黑树结构如下:


    image.png

    节点数为10,大于 UNTREEIFY_THRESHOLD(6),但是根据该方法的逻辑判断,是需要转换为链表的

    • 2、resize的时候,对红黑树进行了拆分

    resize的时候,判断节点类型,如果是链表,则将链表拆分,如果是TreeNode,则执行TreeNode的split方法分割红黑树,而split方法中将红黑树转换为链表的分支如下:

    //在这之前的逻辑是将红黑树每个节点的hash和一个bit进行&运算,
    //根据运算结果将树划分为两棵红黑树,lc表示其中一棵树的节点数
    if (lc <= UNTREEIFY_THRESHOLD)
                        tab[index] = loHead.untreeify(map);
                    else {
                        tab[index] = loHead;
                        if (hiHead != null) // (else is already treeified)
                            loHead.treeify(tab);
                    }
    

    这里才用到了 UNTREEIFY_THRESHOLD 的判断,当红黑树节点元素小于等于6时,才调用untreeify方法转换回链表

    总结

    • 1、hashMap并不是在链表元素个数大于8就一定会转换为红黑树,而是先考虑扩容,扩容达到默认限制后才转换
    • 2、hashMap的红黑树不一定小于6的时候才会转换为链表,而是只有在resize的时候才会根据 UNTREEIFY_THRESHOLD 进行转换。(原因?不太明白hhh)

    相关文章

      网友评论

          本文标题:jdk1.8的hashmap真的是大于8就转换成红黑树,小于6就

          本文链接:https://www.haomeiwen.com/subject/dkhwxhtx.html