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

前言

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

    一、从HashMap中有关“链表转红黑树”阈值的声明;

    二、【重点】解析HashMap.put(K key, V value)的源码;

    三、测试;

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

  在 jdk1.8 HashMap底层数据结构:散列表+链表+红黑树(图解+源码)的 “四、问题探究”中,我有稍微提到过散列表后面跟什么数据结构是怎么确定的:

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

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

二、【重点】解析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个节点,在成功添加了这个新节点之后,立马做链表转红黑树”。

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

  1. 先创建一个Node类,它是HashMap的节点的一个简化:

class Node {
    int info;
    Node next;
    public Node(int info) {
        this.info = info;
    }
}

  2. 简化HashMap的put()方法,并进行测试:

public class A03Method_TreeifyBin {
    public static void main(String[] args) {
        A03Method_TreeifyBin treeifyBin = new A03Method_TreeifyBin();

        //假设下面创建的所有Node都是要放到散列表上的同一个桶上的(即通过计算它们的哈希值定位到了同一个桶):
        Node firstNode = new Node(0);//firstNode作为直接放在桶上的节点。
        for(int i = 1; i < 10; i++) {//已有firstNode,再put进9个节点
            treeifyBin.put(firstNode, new Node(i));//new出的新节点依次链在firstNode后面,形成一个链表。
        }
    }

    /**
     * 该方法截取自HashMap.putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)方法中有关“链表转红黑树的时机”那部分代码。
     * 该方法是对HashMap.putVal(……)源码做简化后的方法,可以参照源码来看该方法。
     *
     * @param p       : 当前节点
     * @param newNode : 要新添进去的节点
     */
    public void put(Node p, Node newNode) {
        Node e;
        for (int binCount = 0;; ++binCount) {
            if ((e = p.next) == null) {
                p.next = newNode;
                if(binCount >= 7) {
                    //HashMap.putVal(……)源码中这里执行了treeifyBin(tab, hash)方法,将链表转为了红黑树。
                    System.out.println("binCount == " + binCount);
                    System.out.println("p.next.info : " + p.next.info);
                    System.out.println("p.info : " + p.info);
                    System.out.println("----------------------------");
                }
                break;
            }
            p = e;
        }
    }
}

  最后的输出结果是:

    binCount == 7
    p.next.info : 8
    p.info : 7
    ----------------------------
    binCount == 8
    p.next.info : 9
    p.info : 8
    ----------------------------

  从输出结果我们可以得知:

    从firstNode后面链上new Node(1)开始,从输出结果可以看到,一旦 binCount == 7 就做链表转红黑树的操作;

    此时"p.next = newNode;"的p是new Node(7),newNode是new Node(8);

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

  

  到这里,有关“HashMap的链表转红黑树的具体时机”算是表述完了,有时间我们再来探究“HashMap的红黑树转回链表的具体时机”~

原文地址:https://www.cnblogs.com/laipimei/p/11282055.html

时间: 2024-10-27 12:19:01

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

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

前言 本文从三个部分去探究HashMap的链表转红黑树的具体时机: 1.从HashMap中有关"链表转红黑树"阈值的声明:2.[重点]解析HashMap.put(K key, V value)的源码:3.测试: 一.从HashMap中有关"链表转红黑树"阈值的声明,简单了解HashMap的链表转红黑树的时机 HashMap中有关"链表转红黑树"阈值的声明: /** * 使用红黑树(而不是链表)来存放元素.当向至少具有这么多节点的链表再添加元素时,

redis源码解析之dict数据结构

dict 是redis中最重要的数据结构,存放结构体redisDb中. typedef struct dict { dictType *type; void *privdata; dictht ht[2]; int rehashidx; /* rehashing not in progress if rehashidx == -1 */ int iterators; /* number of iterators currently running */ } dict; 其中type是特定结构的处

JDK8源码解析 -- HashMap(二)

在上一篇JDK8源码解析 -- HashMap(一)的博客中关于HashMap的重要知识点已经讲了差不多了,还有一些内容我会在今天这篇博客中说说,同时我也会把一些我不懂的问题抛出来,希望看到我这篇博客的大神帮忙解答困扰我的问题,让我明白一个所以然来.彼此互相进步,互相成长.HashMap从jdk7到jdk8版本改变大,1.新增加的节点在链表末尾进行添加  2.使用了红黑树. 1. HashMap容量大小求值方法 // 返回2的幂次 static final int tableSizeFor(in

jdk1.8源码解析(1):HashMap源码解析

jdk1.8 HashMap数据结构 图1-HashMap类图 图2-TreeNode类图 由图1-HashMap类图可知HashMap底层数据结构是由一个Node<K,V>的数组构成.具体Node<K,V>究竟是何数据结构暂且不讨论,先看一下HashMap最重要的两个方法之一put()方法的具体实现 public V put(K key, V value) { return putVal(hash(key), key, value, false, true);} final V

Java源码解析|HashMap的前世今生

HashMap的前世今生 Java8在Java7的基础上,做了一些改进和优化. 底层数据结构和实现方法上,HashMap几乎重写了一套 所有的集合都新增了函数式的方法,比如说forEach,也新增了很多好用的函数. 前世--Java 1.7 底层数据结构 数组 + 链表 在Java1.7中HashMap使用数组+链表来作为存储结构 数组就类似一个个桶构成的容器,链表用来解决冲突,当出现冲突时,就找到当前数据应该存储的桶的位置(数组下标),在当前桶中插入新链表结点. 如下图所示: 链表结点中存放(

OpenJDK1.8.0 源码解析————HashMap的实现(一)

HashMap是Java Collection Framework 的重要成员之一.HashMap是基于哈希表的 Map 接口的实现,此实现提供所有可选的映射操作,映射是以键值对的形式映射:key-value.key——此映射所维护的键的类型,value——映射值的类型,并且允许使用 null 键和 null 值.而且HashMap不保证映射的顺序. 简单的介绍一下HashMap,就开始HashMap的源码分析. 首先简单的介绍一下HashMap里都包含的数据结构.觉得还是先贴一张图比较好,结合

OpenJDK1.8.0 源码解析————HashMap的实现(二)

刚才简单介绍了HashMap的一部分的知识,算是为下面HashMap的进一步学习做准备吧. 刚才一直在思考的一个问题是,这方面的知识网上的资料也是一抓一大把,即使是这样我为什么还要花费时间去写呢.后来我仔细想了一下,其实很简单,虽然大家解读的是同一份源码,但是如果只是看看别人写的文章,源码它真正的思想和魅力你都体会不到一半.所以还是决定自己写写,虽然和别人写的大同小异,但是写完真的能体会到更深层次的东西.再就是我的描述或许不准确甚至说是有错误.希望看到的人可以指出,这样对我也是一种帮助. 然后觉

jdk1.7源码之-hashMap源码解析

背景: 笔者最近这几天在思考,为什么要学习设计模式,学些设计模式无非是提高自己的开发技能,但是通过这一段时间来看,其实我也学习了一些设计模式,但是都是一些demo,没有具体的例子,学习起来不深刻,所以我感觉我可能要换一条路走,所以我现在想法是看一些源码的东西,一方面是因为自己大部分的源码其实没有看过,另一方面源码中可能会涉及到一些编码风格和设计模式的东西,我也可以学习. 使用jdk版本:1.7.0_80 先从最简单的开始: public static void main(String[] arg

[源码解析]HashMap和HashTable的区别(源码分析解读)

前言: 又是一个大好的周末, 可惜今天起来有点晚, 扒开HashMap和HashTable, 看看他们到底有什么区别吧. 先来一段比较拗口的定义: Hashtable 的实例有两个参数影响其性能:初始容量 和 加载因子.容量 是哈希表中桶 的数量,初始容量 就是哈希表创建时的容量.注意,哈希表的状态为 open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索.加载因子 是对哈希表在其容量自动增加之前可以达到多满的一个尺度.初始容量和加载因子这两个参数只是对该实现的提示.