HashMap第5讲——resize方法扩容源码分析及细节

news2024/11/18 15:26:26

put方法的源码和相关的细节已经介绍完了,下面我们进入扩容功能的讲解。

一、为什么需要扩容

这个也比较好理解。假设现在HashMap里的元素已经很多了,但是链化比较严重,即便树化了,查询效率也是O(logN),肯定没有O(1)好,所以需要扩容来降低Hash冲突的概率,提高性能。

二、触发扩容的临界

我们知道,当++size>threshold条件成立时,就会调用resize()方法进行扩容。

ps:不明白的可以去看看第2讲——put方法源码。

三、扩容流程图

我们先看下扩容的流程图,更直观一点:

ps:图片看不清的可以把它下载到本地哦~

总结一下,扩容具体涉及以下三个部分:

  • 如果某桶节点没有形成链表,则直接rehash到其它桶中。

  • 如果桶中的链表已经形成红黑树,将原红黑树节点根据e.hash & oldCap==0条件将原红黑树分为两个红黑树,一部分放在原索引位置,另一部分放在原索引位置+原数组长度位置,且如果最后的结果节点个数小于等于6就转为链表。

  • 如果桶中形成链表,则将链表重新连接:也是根据e.hash & oldCap==0条件将原链表分为两个链表,一部分放在原索引位置,另一部分放在原索引位置+原数组长度位置。

ps:更具体的可以看下面的源码注释

四、源码注释

看之前先看几个重要的参数:

//临界值,当实际大小(容量*负载因子)超过这个值,会进行扩容
int threshold;
//最大长度为2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
 //默认的负载因子为0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//加载因子,默认为0.75
final float loadFactor;
 //存储元素的数组,总是2^n
transient Node<K,V>[] table;

源码注释:

final Node<K,V>[] resize() {
    //原数组
    Node<K,V>[] oldTab = table;
    //原数组容量,为0说明是第一次初始化,反之扩容
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //临界值,当实际大小(容量*负载因子)超过这个值,会进行扩容
    int oldThr = threshold;
    //newCap:新数组长度 newThr:新扩容阈值(threshlod)
    int newCap, newThr = 0;
    if (oldCap > 0) {
        //说明需要扩容
        if (oldCap >= MAXIMUM_CAPACITY) {
            //容量超过上限,无法再扩容,直接返回
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //新数组为原来的2倍 newCap = oldCap << 1
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            //说明新数组长度小于Interger最大值,且原数组长度大于16
            //扩容阈值修改为原来的两倍
            newThr = oldThr << 1; // double threshold
    }
    //ps:下面的两个else说明是要初始化数组
    else if (oldThr > 0) // initial capacity was placed in threshold
        //说明new HashMap(length)时设置了长度
        //那么新数组长度就是threshold(这个可以看我文章的第4讲)
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        //这是没指定长度,新数组长度为16
        newCap = DEFAULT_INITIAL_CAPACITY;
        //扩容阈值为16*0.75=12
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        //说明初始化时指定了长度,但小于16(此时需要计算阈值)
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    //将计算好的新阈值赋给threshold
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
            //初始化新Node数组
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        //ps:这是要进行真正的扩容了
        for (int j = 0; j < oldCap; ++j) {
            //遍历老数组
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                //ps:当前位置有元素
                //将当前位置置空
                oldTab[j] = null;
                if (e.next == null)
                    //只有一个元素,说明还没链化。
                    //将该元素进行rehash
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    //ps:已经树化。这里就简单概括下
                    //把原tree一分为二,分别放到原数组位置和原数组长度+索引位置
                    //这两部分:如果元素数量小于等于6个就转为链表
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    //ps:链化
                    //也是一份为二,lo:原索引位置,hi:索引位置+原数组长度位置
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        //ps:根据e.hash & oldCap分类
                        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;
}

五、为什么负载因子默认是0.75

我们知道当HashMap容量大于threshlod(阈值)时,会触发扩容操作,而threshlod=负载因子(loadFactory)*容量(capacity)。

loadFactory默认值为0.75(不会轻易修改),也就是当HashMap中元素个数达到容量的3/4时会进行自动扩容,那么为什么是负载因子为什么默认是0.75呢?

这里JDK官方文档中那个也给出了原因,大概意思是:

一般来说,默认负载因子(0.75)在时间和空间成本之间提供了很好的权衡。更高的值减少了空间开销,但增加了查找成本(在HashMap大多数操作中,包括get和put)。

我们可以逆向假设下:假设负载因子为1,也就是达到最大容量时再扩容,那么在HashMap中,最好的情况是这16个元素分别落在16个桶中,否则就必然会发生hash碰撞,而且元素越多,碰撞的几率就越大,查找速度也会越低,显然不合理。

所以负载因子不能太大,不然会导致大量的hash冲突,也不能太小,那样会浪费空间。

题外话:

Stack Overflor有一篇文章通过一个公式计算出负载因子值得最佳选择是在0.693,也就是0.7左右。

那为啥最终选择的是0.75呢?因为threshold=loadFactory*capacity,其中capacity永远是2^n,为了保证它俩乘积是个整数,因为0.75和任何2^n(n>1)乘积都是整数,所以才选择了0.75。

 End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。

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

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

相关文章

最新MDYS14源码影视视频网站模板/苹果CMS系统/附搭建教程

最新MDYS14源码影视视频网站模板/苹果CMS系统/附搭建教程 基本介绍&#xff1a; 1、后台增加自定义参数&#xff0c;对应会员升级页面&#xff0c;以及积分充值 2、视频&#xff0c;演员&#xff0c;专题&#xff0c;收藏&#xff0c;会员系统模块齐全&#xff0c;支持子分类…

本地读取classNames txt文件

通过本地读取classNames,来减少程序修改代码,提高了程序的拓展性和自定义化。 步骤: 1、输入本地路径,分割字符串。 2、将className按顺序放入vector容器中。 3、将vector赋值给classNmaes;获取classNames.size(),赋值给CLASSES;这样,类别个数和类别都已经赋值完成。…

阀门盘根的介绍

盘根&#xff08;编制盘根&#xff09;&#xff08;packing&#xff09;也叫密封填料&#xff0c;通常由较柔软的线状物编织而成&#xff0c;通常截面积是正方形或长方形、圆形的条状物填充在密封腔体内,从而实现密封。填料密封最早是以棉麻等纤维塞在泄漏通道内来阻止液流泄漏…

牛客挑战赛75 D. 不存在的玩家(sg图dp)

题目 思路来源 灵茶山群群友 https://blog.csdn.net/Code92007/article/details/110354429 题解 其实想了想&#xff0c;和20年小米邀请赛决赛这个G题的dp思路是一样的&#xff0c;姑且称为sg图dp吧 按sg值从大到小dp&#xff0c;每次补上全局sg值-1的这些点&#xff0c; …

XML简介XML 使用教程XML的基本结构XML的使用场景

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

VCS编译bug汇总

‘typedef’ is not expected to be used in this contex 注册前少了分号。 Scope resolution error resolution : 声明指针时 不能与类名同名&#xff0c;即 不能声明为adapter. cannot find member "type_id" 忘记注册了 拼接运算符使用 关键要加上1b&#xff0…

开发板以电脑为跳板连接互联网

标题 开发板以电脑为跳板连接互联网网络共享方式桥接方式 开发板以电脑为跳板连接互联网 分享下用网线直连电脑的开发板如何以电脑为跳板连接互联网的两个方法。 网络共享方式桥接方式 补充下&#xff0c;我的电脑连接的是无线网络&#xff0c;开发板和电脑是用网线进行连接的…

Microsoft Teams新版升级或安装方法

Microsoft Teams作为一款国际化公司会议软件&#xff0c;在2024年7月1日起不再支持经典版本&#xff0c;提示升级New Teams。 由于New Teams官网提供的Windows系统安装包并不是传统的可执行文件MSI&#xff0c;EXE等&#xff0c;而是新型封装的MSIX格式&#xff0c;无法直接双击…

Vitis IDE 艰难切换--从传统 Vitis GUI 到 2024.1 统一软件界面

目录 1. 简介 2. 界面展示 2.1 启动方式 2.2 Settings 对比 3. 创建 HLS 工程 3.1 选择 HLS 组件 3.2 名称和路径 3.3 创建配置文件 3.4 选择综合源文件和TestBench 3.5 选择硬件平台 3.6 配置Clock和Flow 3.7 查看摘要 3.8 新建组件完毕 4. 总结 1. 简介 Vitis…

海南聚广众达电子商务咨询有限公司抖音电商服务专家

在当下这个数字化浪潮汹涌的时代&#xff0c;电子商务无疑是商业领域的一颗璀璨明星。而在这其中&#xff0c;抖音电商以其独特的魅力&#xff0c;吸引了无数目光。海南聚广众达电子商务咨询有限公司&#xff0c;作为抖音电商领域的佼佼者&#xff0c;以其敏锐的洞察力和卓越的…

无人机操作注意事项

检查飞行设备 每次飞行前&#xff0c;要认真检查无人机的各处细节&#xff0c;遥控器等地面设备也不例外。 确保设备电量充足 起飞前&#xff0c;检查无人机是否电量充足&#xff0c;以及辅助设备如遥控器、手机等。 选择空旷的飞行场地 选择适宜的场地进行操作&#xff0…

“我,前YC学员,做了新创业项目——用AI把帽子空投给纽约客”

大数据产业创新服务媒体 ——聚焦数据 改变商业 当大部分工程师还在用AI技术打造改变世界的产品&#xff0c;建立自己的商业帝国时&#xff0c;纽约的创业者James Steinberg另辟蹊径&#xff0c;研究如何利用AI把帽子精准地扔给路过自家楼下的路人。 不得不说&#xff0c;这种…

Uniapp打包苹果app证书过期操作流程+辅助工具【香蕉云编】(没有苹果电脑可以使用香蕉云编,有的另说)

1、登录香蕉云编&#xff0c;创建ios证书 2、登录苹果开发者&#xff0c;在【Certificates】创建新的描述并且将【1步骤中csr文件】上传&#xff0c;后 创建成功后将后缀为【cer】文件下载下来&#xff0c; 然后到香蕉云编中上传cer文件&#xff0c;并生成p12文件 3、删除过期文…

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(十八)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 28 节&#xff09; P28《27.网络连接-Http请求数据》 案例&#xff1a; 这里不懂后端假设服务器的前端小伙伴就需要课程源码资料了…

网易严选礼品卡有什么用?

网易严选的礼品卡可以在网易商城里买东西 但是现在好多人买东西基本上都用的是淘宝京东之类的 很少会有人用网易吧 但是最近我朋友送了我几张网易的卡&#xff0c;我自己也用积分兑换一张&#xff0c;一直不知道怎么用 最后还是在收卡云上转让出去了&#xff0c;价格高不说…

引用别的组件

在脚本中&#xff0c;也可以引用别的物体下的组件。 第一种办法&#xff0c; &#xff08;1&#xff09;添加一个变量&#xff0c; public GameObject bgmNode ; 然后在检查器里指定这个引用。 例如&#xff1a;在背景音乐空物体下面有个Audio Source组件 游戏主控脚本代码…

简单的springboot整合activiti5.22.0

简单的springboot整合activiti5.22.0 1. 需求 我们公司原本的流程服务是本地workflow模块以及一个远程的webService对应的activiti服务&#xff0c;其中activiti版本为5.22.0&#xff0c;之前想将activiiti5.22.0进行升级&#xff0c;选择了camunda&#xff0c;也对项目进行了…

Houdini 通过wedge来做模拟参数对比 (PDG TOP)

我们的设定如下例子 这是个简单的布料悬挂的例子。上方两个角分别被固定住了&#xff0c;然后在distance约束下布料下垂。 我们现在的目的是想要对比不同的streach stiffness对模拟的影响。 第一步&#xff1a;找到stiffness参数&#xff0c;右键expression->edit expre…

centos7迁移部分成功

早闻CentOS不再维护的消息&#xff0c;确实有些遗憾&#xff0c;毕竟这个系统好用又简单&#xff0c;已经成为了我们工作中的一种习惯。然而&#xff0c;2024年6月30日这一天如约而至&#xff0c;CentOS 7停止维护后&#xff0c;随之而来的安全漏洞又该如何防范&#xff1f;系统…

华为云x86架构下部署mysql

华为云x86架构下部署mysql 1. 配置X86架构ESC2. 查看本系统中有没有安装mariadb相关的组件&#xff0c;有则卸载3. 安装mysql4. 启动mysql5. 登录MySQL&#xff0c;修改密码&#xff0c;开放访问权限 1. 配置X86架构ESC 2. 查看本系统中有没有安装mariadb相关的组件&#xff0c…