HashMap核心方法:put()、putVal()、resize()与treeifyBin()

news2024/11/24 17:20:38

一、put方法

put方法的源码如下:
在这里插入图片描述
由此可见put的核心方法为putVal()
putVal方法各参数值讲解:
在这里插入图片描述
前面三个参数不做过多讲解,第4个参数是控制是否要覆盖原来key中已经存在的值,比如HashMap的putIfAbsent方法调的也是putVal方法,它的第4个参数传的值为true,因此putIfAbsent方法不会覆盖原有的值。
在这里插入图片描述

二、putVal方法

putVal方法的源码解析如下:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 如果数组为空,进行 resize() 初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // (n - 1) & hash相当于取模,获取数组的索引位置
        // 如果计算的位置上Node不存在,直接创建节点插入
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            // 如果计算的位置上Node 存在,链表或者红黑树处理
            Node<K,V> e; K k;
            // 如果已存在的key和传入的key一模一样,则需要覆盖
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            // 如果 index 位置元素已经存在,且是红黑树
            else if (p instanceof TreeNode)
                // 将元素put到红黑树中
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                // 否则如果是链表的情况,对链表进行遍历,并统计链表长度
                for (int binCount = 0; ; ++binCount) {
                    // 如果节点链表的next为空
                    if ((e = p.next) == null) {
                        // 找到节点链表中next为空的节点,创建新的节点插入
                        p.next = newNode(hash, key, value, null);
                        // 如果节点链表中数量超过TREEIFY_THRESHOLD(8)个,转化为红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            // 树化
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 判断节点链表中的key和传入的key是否一样
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        // 如果一样的话,退出
                        break;
                    p = e;
                }
            }
            // 如果存在相同key的节点e不为空
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                // onlyIfAbsent 表示是否仅在 oldValue 为 null 的情况下更新键值对的值
                if (!onlyIfAbsent || oldValue == null)
                    // 设置新的值
                    e.value = value;
                afterNodeAccess(e);
                // 返回旧的结果
                return oldValue;
            }
        }
        ++modCount;
        // 当前大小大于临界大小,扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
}

2.1、补充点

  1. put方法第5个参数evict在hashMap中并没有实际意义,只有在子类方法中才会使用到。
    put方法第5个参数evict只调用了afterNodeInsertion(evict),而afterNodeInsertion(evict)是一个空方法,如下图所示。
    在这里插入图片描述
  2. (n - 1) & hash
    (n - 1) & hash相当于取模,但与计算比取模的效率要高。
    在这里插入图片描述

三、resize方法

resize方法是一个扩容的方法,同时也是一个初始化HashMap的方法。
resize方法的源码解析如下:

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        // 现有容量的大小,等于数组的长度,如果数组为空,返回0
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // 现有的扩容阈值
        int oldThr = threshold;
        // newCap表示新的容量,newThr新的扩容阈值
        int newCap, newThr = 0;
        // 如果现有容量大于0,表示已经初始化过了
        if (oldCap > 0) {
            // 如果现有容量已经大于最大容量。结束扩容,直接返回
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
             // 否则,如果扩大两倍之后的容量小于最大容量,且现有容量大于等于初始容量16    
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                 // 新的扩容阀值扩大为两倍,左移<<1 相当于乘以2
                newThr = oldThr << 1; // double threshold
        }
        // 否则如果当前容量等于0 ,但是当前扩容阈值 > 0,调用有参构造函数会到这里
        else if (oldThr > 0) // initial capacity was placed in threshold
             // 进入这里,新的容量等于当前的扩容阈值,
            newCap = oldThr;
        // 否则如果当前容量等于0,并且挡墙扩容阈值=0,调用无参构造函数进入这里
        else {               
            // 新的容量等于默认容量
            newCap = DEFAULT_INITIAL_CAPACITY;
            // 新的扩容阈值等于默认负载因子0.75*默认容量16=12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // 如果新的扩容阈值等于0
        if (newThr == 0) {
            // 设置新的扩容阈值等于新的容量*负载因子
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
       // 设置hashmap对象的扩容阈值位新的扩容阈值
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        // 初始化数组     
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        // 设置hashmap对象的桶数组为newTab
        table = newTab;
        // 下面时rehash的过程
         // 如果旧的桶数组不为空,则遍历桶数组,并将键值对映射到新的桶数组中
        if (oldTab != null) {
            // 遍历老的数组
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                // 如果数组索引位置不为空
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    // 如果节点下面没有链表或者红黑树
                    if (e.next == null)
                        // 用新数组容量取模,设置到新数组中
                        newTab[e.hash & (newCap - 1)] = e;
                    // 如果节点是红黑树    
                    else if (e instanceof TreeNode)
                        // 需要对红黑树进行拆分
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    // 如果节点是链表 
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                         // 遍历链表,并将链表节点按原顺序根据高低位分组
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                         // 将分组后的链表映射到新桶中
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
}

3.1、补充点

  1. 扩容后链表的处理方法:
    在这里插入图片描述
    在这里插入图片描述
  2. 扩容后红黑树的处理方法:
    红黑树的拆分也是分低位和高位的。如果拆分后节点大于6个,则还是红黑树、如果节点数小于6个,则要退化成链表。

四、treeifyBin方法

treeifyBin方法将普通节点链表转换成树形节点。

扩容树化要满足两个条件:

  1. 链表长度大于等于 8
  2. 桶数组容量大于等于64

treeifyBin方法的源码解析如下:

final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        // 桶数组容量小于 MIN_TREEIFY_CAPACITY,优先进行扩容而不是树化
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            // hd 为头节点(head),tl 为尾节点(tail)
            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);
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/995024.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Reinforcement Learning for Solving the Vehicle Routing Problem

Reinforcement Learning for Solving the Vehicle Routing Problem 一、背景二、模型三、公式 一、背景 本篇论文讨论一种有容量限制的版本CVRP&#xff0c;一辆有有限容量的车辆负责向地理分布的、需求有限的客户交付物品&#xff1b;当车辆的负载耗尽&#xff0c;它返回仓库…

MyBatis中一对一、一对多和多对多关联关系的配置详解

MyBatis中一对一、一对多和多对多关联关系的配置详解 引言一对一关联关系配置一对多关联关系配置结论 多对多关联关系配置结论 引言 MyBatis是一款优秀的持久层框架&#xff0c;它提供了灵活且强大的关联关系配置功能。本文将介绍MyBatis中一对一、一对多和多对多关联关系的配置…

SAP MTS案例教程AA固定资产后台配置

目录 本章介绍 2 AA通用后台配置 3 检查国家特殊的设置 3 创建折旧表 4 分配折旧表到公司代码 5 指定帐户确定 6 创建屏幕格式规则 7 定义号码范围间隔 8 定义资产分类 9 为资产类别分配总账科目 11 分配非税购置的进项税标识符 13 指定间隔和过帐规则 14 设置当前的折旧表 15…

使用Mybatis实现基本的增删改查------数据输入

创建一个空的Maven项目,删去src,用作存储pom的父项目 pom中存放下列依赖: <dependencies><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.11</version></dependency><de…

系列二、Nginx简介

一、概述 Nginx是一个高性能的HTTP和反向代理服务器&#xff0c;处理高并发的能力十分强大&#xff0c;能够经受的住高负载的考验&#xff0c;有报告表明其能支持高达50000个并发连接数。 二、代理分类 2.1、正向代理 如果把局域网外的Internet想象成一个巨大的资源库&#x…

基于SSM的家居商城系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

算法-分治算法

文章来源&#xff1a; https://blog.csdn.net/weixin_45630258/article/details/126425400 欢迎各位大佬指点、三连 一、分治 1、定义&#xff1a;分治&#xff0c;也就是分而治之。 它的一般步骤是&#xff1a; ① 将原问题分解成若干个规模较小的子问题&#xff08;子问题…

【计算机网络】OSI七层网络模型概述及应用举例

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 更多计算机网络知识专栏&#xff1a;计算机网络&#x1f525; 给大家跳段…

Hadoop分布式模式配置

hadoop环境准备&#xff1a; hadoop下载地址&#xff1a;http://archive.apache.org/dist/hadoop/common/hadoop-3.1.3/hadoop-3.1.3.tar.gz hadoop集群的安装配置大致分为以下六个步骤&#xff1a; 选定一台机器作为master 在master节点上创建hadoop用户、安装ssh服务端、配…

春秋云镜 CVE-2016-0785

春秋云镜 CVE-2016-0785 S2-029 靶标介绍 2.3.28 之前的 Apache Struts 2.x 允许远程攻击者通过标签属性中的“%{}”序列执行任意代码。 启动场景 漏洞利用 工具利用 得到flag flag{a4c7fc9a-8e2d-49b8-9b09-22790fb2bfb6}

APO 载脂蛋白

Apolipoprotein E structure: insights into function-2006 Apolipoprotein E: Structural Insights and Links to Alzheimer Disease Pathogenesis APO-E 突变基因检测&#xff1b; APO-D, APO-M ; lipocalin ; APO-H CCP , 凝血诊断&#xff0c;狼疮抗凝物&#xff0…

Spring框架入门:构建你的第一个Web应用

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Matlab图像处理-多阈值分割

多阈值分割 在某些时候图像使用单独的阈值不能够对其实现有效地分割&#xff0c;例如在灰度直方图中有明显的三个峰时候&#xff0c;我们需要提取中间峰&#xff0c;这时我们使用双阈值分割会得到较好的分割效果。如下例子中生成灰度直方图中有两个峰&#xff0c;选择合适的两…

【strstr函数的介绍和模拟实现——超详细版】

strstr函数的介绍和模拟实现 strstr函数的介绍 资源来源于cplusplus网站 strstr函数声明&#xff1a; char *strstr( const char *str1, const char *str2 ); 它的作用其实就是&#xff1a; 在字符串str1中查找是否含有字符串str2&#xff0c;如果存在&#xff0c;返回str2在…

数据安全服务是什么意思?

数据安全服务是指为保护用户的数据免受未经授权的访问、修改、损坏或泄露的服务。随着信息化的发展&#xff0c;大量的个人和企业数据被存储在网络上&#xff0c;数据安全问题也日益受到关注。数据安全服务旨在帮助用户识别和应对各种潜在的数据安全风险&#xff0c;并提供相应…

支付宝沙箱调用错误

支付宝沙箱调用参数就三个APPID&#xff0c;用户私钥&#xff0c;支付宝公钥&#xff0c; 发送请求需要的配置 alipay: app_id: 2021000122636644 merchant_private_key: 用户私钥 alipay_public_key: 支付宝公钥 sign_type: RSA2 charset: utf-8 gatewayUrl: https://openap…

企业架构LNMP学习笔记30

1、upstream 中server的关键字&#xff1a;语法&#xff1a; upstream中的分发之后的几个关键字&#xff1a; 1&#xff09;backup 备 其他的没有backup标识的都不可用了&#xff0c;才分发到backup&#xff1b; 2&#xff09;down 此条配置&#xff0c;不会被分发到。 syst…

Android Studio实现一笔画完小游戏

文章目录 一、项目概述二、开发环境三、详细设计3.1、数据库设计3.2、普通模式3.3、随机模式3.4、关卡列表 四、运行演示五、项目总结六、源码获取 一、项目概述 Android一笔画完是一种益智游戏&#xff0c;玩家需要从起点开始通过一条连续的线&#xff0c;将图形中所有的方块…

谷歌浏览器书签位置及怎么导入书签

谷歌浏览器书签位置&#xff1f; 在谷歌浏览器地址栏输入chrome://version/ 按回车键打开谷歌浏览器安装信息在显示的个人资料路径查找到Bookmarks文件&#xff0c;及为谷歌浏览器书签文件 谷歌浏览器怎么导入书签&#xff1f; 将Bookmarks加入.html后缀在书签管理器中找到右…

可观测性在灰度发布中的应用

前言 随着云计算的发展、云原生时代的来临&#xff0c;企业数字化转型进程不断深入&#xff0c;应用开发也越来越多地基于微服务化模式&#xff0c;快速迭代的能力使得应用开发更高效、更灵活。同时&#xff0c;也不得不面临应用版本快速升级所带来的的巨大挑战。 传统的发布方…