美文网首页Java架构技术进阶码出未来Java成长之路
HashMap底层数据结构之链表转红黑树的具体时机

HashMap底层数据结构之链表转红黑树的具体时机

作者: 码上搞定 | 来源:发表于2019-08-07 10:39 被阅读5次

前言

本文从三个部分去探究HashMap的链表转红黑树的具体时机:

1、从HashMap中有关“链表转红黑树”阈值的声明;
2、【重点】解析HashMap.put(K key, V value)的源码;
3、测试;

一、从HashMap中有关“链表转红黑树”阈值的声明,简单了解HashMap的链表转红黑树的时机

HashMap中有关“链表转红黑树”阈值的声明:

/**
    *  使用红黑树(而不是链表)来存放元素。当向至少具有这么多节点的链表再添加元素时,链表就将转换为红黑树。
    * 该值必须大于2,并且应该至少为8,以便于删除红黑树时转回链表。
    */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     *  当桶数组容量小于该值时,优先进行扩容,而不是树化:
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

二、【重点】解析HashMap.put(K key, V value)的源码,去弄清楚链表转红黑树的具体时机

通过查看HashMap的源码可以发现,它的put(K key, V value)方法调用了putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)来实现元素的新增。所以我们实际要看的是putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)的源码。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        Node<K, V>[] tab;
        Node<K, V> p;
        int n, i;
        if ((tab = table) == null || (n = tab.length) == 0) 
            n = (tab = resize()).length; 
        if ((p = tab[i = (n - 1) & hash]) == null) 
            tab[i] = newNode(hash, key, value, null); //直接放在散列表上的节点,并没有特意标识其为头节点,其实它就是"链表/红黑树.index(0)"
        else { 
            Node<K, V> e;
            K k;
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode) 
                e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value); 
       else { //下面的代码是探究“链表转红黑树”的重点:
                for (int binCount = 0;; ++binCount) {
                    if ((e = p.next) == null) { //沿着p节点,找到该桶上的最后一个节点:
                        p.next = newNode(hash, key, value, null); //直接生成新节点,链在最后一个节点的后面;

                        //“binCount >= 7”:p从链表.index(0)开始,当binCount == 7时,p.index == 7,newNode.index == 8; 
                        //也就是说,当链表已经有8个节点了,此时再新链上第9个节点,在成功添加了这个新节点之后,立马做链表转红黑树。
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                treeifyBin(tab, hash); //链表转红黑树 break; 
            }
            …… 
            p = e; 
          } 
       } 
       …… 
    } 
    …… 
}

通过源码解析,我们已经很清楚HashMap是在“当链表已经有8个节点了,此时再新链上第9个节点,在成功添加了这个新节点之后,立马做链表转红黑树”。

三、通过debug,进一步理解链表转红黑树的具体时机

1. 自定义一个类:该类中去重写hashCode(),让一组数据能得到同样的哈希值,从而实现哈希碰撞。同时也重写equals()方法。

public class A03Bean {
    protected int number;
    
    public A03Bean(int number) {
        this.number = number;
    }
    
    /**
     * 重写hashCode()方法,只要是4的倍数,最后算出的哈希值都会是0.
     */
    @Override
    public int hashCode() {
        return number % 4;
    }
    
    /**
     * 也必须重写equals()方法。当发生哈希冲突的时候,需要调用equals()方法比较两个对象的实际内容是否相同。
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        A03Bean other = (A03Bean) obj;
        if (number != other.number)
            return false;
        return true;
    }
}

2. 将自定义类A03Bean的实例放到HashMap中:

public class A03Method_TreeifyBin2 {
    public static void main(String[] args) {
        HashMap<A03Bean, Integer> hashMap = new HashMap<>();
        hashMap.put(new A03Bean(4), 0);
        hashMap.put(new A03Bean(8), 1);
        hashMap.put(new A03Bean(12), 2);
        hashMap.put(new A03Bean(16), 3);
        hashMap.put(new A03Bean(20), 4);
        hashMap.put(new A03Bean(24), 5);
        hashMap.put(new A03Bean(28), 6);
        hashMap.put(new A03Bean(32), 7);
        hashMap.put(new A03Bean(36), 8);
        hashMap.put(new A03Bean(40), 9);
        hashMap.put(new A03Bean(44), 10);
        
        System.out.println("hashMap.size = " + hashMap.size());
        
        //查看是否所有对象都放到HashMap中了:
        for(A03Bean key : hashMap.keySet()) {
            System.out.println(key.number);
        }
    }
}

3.debug,断点查看当同一个桶上的链表的长度达到多长时会做“链表转红黑树”的操作。

断点打在HashMap.putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)方法的“treeifyBin(tab, hash);”这里。

4.测试结果:

当put进第9个元素(hashMap.put(new A03Bean(36), 8);)时,HashMap做了链表转红黑树的操作。

也就是说:当链表已经有8个元素了,此时put进第9个元素,先完成第9个元素的put,然后立刻做链表转红黑树。这个结论和第2点中得到的结论一致。

最后的输出结果也证明了所有的元素都成功put进了集合中,hashMap.size等于11。

到这里,有关“HashMap的链表转红黑树的具体时机”算是解释清楚了,有时间再探究“HashMap的红黑树转回链表的具体时机”。

相关文章

  • HashMap底层数据结构之链表转红黑树的具体时机

    前言 本文从三个部分去探究HashMap的链表转红黑树的具体时机: 1、从HashMap中有关“链表转红黑树”阈值...

  • 2019-7-19

    部分常用容器类 HashMap 底层数据结构,数组 + 链表/红黑树链表类 - Node - 单链表 红黑树类 -...

  • HashMap常见面试题解析

    HashMap的底层数据结构? 数组+链表 , 数组+链表+红黑树 HashMap的存取原理? 通过获取key对象...

  • HashMap源码学习

    HashMap底层数据结构 HashMap底层数据结构是单链表数组+红黑树,数组默认大小16,最大1<<30,默认...

  • HashMap底层原理

    HashMap HashMap底层数据结构 JDK1.7及之前:数组+链表 JDK1.8:数组+链表+红黑树 Ha...

  • HashMap(1.8)源码分析

    HashMap底层使用的数据结构是数组+链表/红黑树。 1 HashMap中的静态常量 DEFAULT_INITI...

  • Android面试题知识点积累(七)

    jdk1.8中HashMap底层链表转红黑树的阈值为什么是8?红黑树转链表为什么是6? 和hashcode碰撞次数...

  • Java 面试问题系列五(HashMap)

    1、HashMap的原理,内部数据结构? 底层使用哈希表(数组 + 链表),当链表过长会将链表转成 红黑树以实现 ...

  • 数据结构

    HashMap的原理,内部数据结构?底层使用哈希表(数组 + 链表),当链表过长会将链表转成 红黑树以实现 O(l...

  • 老猿说说-HashMap

    1. 概述 HashMap 底层的数据结构主要是:数组 + 链表 + 红黑树。其中当链表的长度大于等于 8 时,链...

网友评论

    本文标题:HashMap底层数据结构之链表转红黑树的具体时机

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