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

前言

本文从三个部分去探究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的红黑树转回链表的具体时机”。

原文地址:https://blog.51cto.com/14230003/2426952

时间: 2024-11-03 21:02:31

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

jdk1.8源码解析:HashMap底层数据结构之链表转红黑树的具体时机

前言 本文从三个部分去探究HashMap的链表转红黑树的具体时机: 一.从HashMap中有关“链表转红黑树”阈值的声明: 二.[重点]解析HashMap.put(K key, V value)的源码: 三.测试: 一.从HashMap中有关“链表转红黑树”阈值的声明,简单了解HashMap的链表转红黑树的时机 在 jdk1.8 HashMap底层数据结构:散列表+链表+红黑树(图解+源码)的 “四.问题探究”中,我有稍微提到过散列表后面跟什么数据结构是怎么确定的: HashMap中有关“链表转

HashMap什么时候会触发链表转红黑树

日常工作中,被同事突然问到的一个问题,hashmap是我们JAVA程序中使用频率非常高的key-value键值对形式的数据类型 结论是目前能触发转化的两个条件是:一个是链表的长度达到8个,一个是数组的长度达到64个 为什么要触发这个转换,目前官方的解释: Because TreeNodes are about twice the size of regular nodes, we use them only when bins contain enough nodes to warrant us

jdk1.8 HashMap底层数据结构:深入解析为什么jdk1.8 HashMap的容量一定要是2的n次幂

前言 1.本文根据jdk1.8源码来分析HashMap的容量取值问题: 2.本文有做 jdk1.8 HashMap.resize()扩容方法的源码解析:见下文“一.3.扩容:同样需要保证扩容后的容量是2的n次幂”: 3.目录: 一.jdk1.8中,对“HashMap的容量一定是2的n次幂”做了严格控制 1.默认初始容量 2.使用HashMap的有参构造函数来自定义容量的大小(保证容量是2的n次幂) 3.扩容:同样需要保证扩容后的容量是2的n次幂( jdk1.8 HashMap.resize()扩

[转]java 的HashMap底层数据结构

java 的HashMap底层数据结构 HashMap也是我们使用非常多的Collection,它是基于哈希表的 Map 接口的实现,以key-value的形式存在.在HashMap中,key-value总是会当做一个整体来处理,系统会根据hash算法来来计算key-value的存储位置,我们总是可以通过key快速地存.取value.下面就来分析HashMap的存取. 一.定义 HashMap实现了Map接口,继承AbstractMap.其中Map接口定义了键映射到值的规则,而AbstractM

java 的HashMap底层数据结构

HashMap也是我们使用非常多的Collection,它是基于哈希表的 Map 接口的实现,以key-value的形式存在.在HashMap中,key-value总是会当做一个整体来处理,系统会根据hash算法来来计算key-value的存储位置,我们总是可以通过key快速地存.取value.下面就来分析HashMap的存取. 一.定义 HashMap实现了Map接口,继承AbstractMap.其中Map接口定义了键映射到值的规则,而AbstractMap类提供 Map 接口的骨干实现,以最

【C/C++学院】0828-STL入门与简介/STL容器概念/容器迭代器仿函数算法STL概念例子/栈队列双端队列优先队列/数据结构堆的概念/红黑树容器

STL入门与简介 #include<iostream> #include <vector>//容器 #include<array>//数组 #include <algorithm>//算法 using namespace std; //实现一个类模板,专门实现打印的功能 template<class T> //类模板实现了方法 class myvectorprint { public: void operator ()(const T &

HashMap底层数据结构

1.    HashMap概述: HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 2.    HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外.HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体. 从上图中可以看出,HashM

数据结构Java版之红黑树(八)

红黑树是一种自动平衡的二叉查找树,因为存在红黑规则,所以有效的防止了二叉树退化成了链表,且查找和删除的速度都很快,时间复杂度为log(n). 什么是红黑规则? 1.根节点必须是黑色的. 2.节点颜色要么是红要么是黑. 3.树的每一个分叉存在相同黑色节点. 4.不允许存在两个连续的红色节点. 为不断适应红黑规则,在写程序中如何调整? 1.旋转 ---单旋转 外侧节点单旋转.外侧节点指的是左子树的左孩子节点,右子树的右孩子节点. ---双旋转 内侧节点双旋转.内侧节点指的是左子树的右孩子节点,右子树

研磨数据结构与算法-14红黑树

红黑树: public class RBTree { private final Node NIL = new Node(null,null,null,Color.BLACK,-1); private Node root; public RBTree() { root = NIL; } public RBTree(Node  root) { this.root = root; } //插入节点 public void rbInsert(Node node) { Node previous = N