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

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:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Proxmox VE(PVE)上手配置指南

Proxmox VE&#xff08;PVE&#xff09;是一款开源虚拟化管理平台&#xff0c;集成了KVM和LXC技术&#xff0c;支持虚拟机和容器管理。它提供了一个基于Web的用户界面&#xff0c;支持高可用性集群、备份和恢复、实时迁移等功能&#xff0c;适用于企业级虚拟化环境。. 以下为安…

安装GroudingDINO RuntimeError: Error compiling objects for extension,如何解决?

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

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…

opencascade AIS_InteractiveContext源码学习6 management of active Selection Modes

AIS_InteractiveContext 前言 交互上下文&#xff08;Interactive Context&#xff09;允许您在一个或多个视图器中管理交互对象的图形行为和选择。类方法使这一操作非常透明。需要记住的是&#xff0c;对于已经被交互上下文识别的交互对象&#xff0c;必须使用上下文方法进行…

计算机网络期末复习(大题+小题)

计算机网络期末复习 一、计算机网络概述 Point 1 计算机网络就是以传输信息为基本目的&#xff0c;用通信线路和通信设备将多个计算机连接起来的计算机系统的集合。由自治的计算机互联起来的结合体。 Point 2 按网络的覆盖范围进行分类 &#xff08;1&#xff09;局域网*…

海富泰可直动式比例阀控制器EVRD-03C26SB-C1D24-B00

控制EVOTEK海富泰可直动式及先导式比例方向阀EVRD-03A04SA-C1D24-V00、EVRD-03C08SB-C1D24-B00、EVRD-03A16SA-C1D24-V00、EVRD-03C26SB-C1D24-B00、EVRD-05A30SA-C1D24-V00、EVRD-05C60SB-C1D24-B00、EVRD-P05A80SA-IIC1D24-B00、EVRD-P07C100SB-EEC1D24-V00、EVRD-P07A150SA-…

Appium+python自动化(二十八)- 滑呀滑,滑到奈何桥喝碗孟婆汤 - 高级滑动(超详解)

简介   奈何桥上叹奈何&#xff0c;三生石前憾三生&#xff0c;彼岸花下非彼岸&#xff0c;奈何三生彼岸人。 相传过了鬼门关便上一条路叫黄泉路&#xff0c;路上盛开着只见花&#xff0c;不见叶的彼岸花。花叶生生两不见&#xff0c;相念相惜永相失&#xff0c;路尽头有一条…

JAVA医院绩效考核系统源码:绩效考核的重要性、绩效管理分配实践具体实操,基于B/S架构开发的一套(公立医院绩效考核系统源码)

JAVA医院绩效考核系统源码&#xff1a;绩效考核的重要性、绩效管理分配实践具体实操&#xff0c;基于B/S架构开发的一套&#xff08;公立医院绩效考核系统源码&#xff09; 系统开发环境 开发语言&#xff1a;java 技术架构&#xff1a;B/S架构 开发工具&#xff1a;maven、…

LeetCode 算法:验证二叉搜索树 c++

原题链接&#x1f517;&#xff1a;验证二叉搜索树 难度&#xff1a;中等⭐️⭐️ 题目 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左 子树 只包含 小于 当前节点的数。节点的右子树只包含 大于…

锐起RDV5高性能云桌面

锐起是上海锐起信息技术有限公司旗下品牌。该公司创立于 2001 年&#xff0c;是桌面虚拟化产品和解决方案提供商&#xff0c;专注于桌面管理系统和私有云存储系统的系列软件产品研发&#xff0c;致力于简化 IT 管理、增强系统安全&#xff0c;提供简单、易用、稳定、安全的产品…

DockerDesktop中mysql容器无法使用Exec窗口解决

解决前 需要登陆&#xff1a; 登陆后需要升级才能启动调试模式 需要订阅才能使用 解决后&#xff1a; 正常使用 解决方法&#xff1a; 不要在DockerDesktop中启动mysql容器&#xff0c;使用命令行启动 启动命令 docker run --name mysql_docker -e MYSQL_ROOT_PASSWORD12345…

【单片机毕业设计选题24030】-基于STM32的智能鱼缸设计

系统功能: 采用STM32最小系统板控制&#xff0c;采集传感器数据显示在OLED上 并通过继电器进行相应的操作。 系统操作说明&#xff1a; 上电后OLED显示 “欢迎使用智能鱼缸系统请稍后”&#xff0c;两秒后进入第一页面显示。 第一页面第一行显示“系统状态监测”&#xff…

阀门盘根的介绍

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

不是KVM不支持精简置备的磁盘,而是VMM

正文共&#xff1a;999 字 11 图&#xff0c;预估阅读时间&#xff1a;1 分钟 书接上文&#xff08;不会吧&#xff01;KVM竟然不支持磁盘的精简置备&#xff01;&#xff1f;&#xff09;&#xff0c;我们已经掌握了通过“虚拟系统管理器VMM”创建虚拟机的基本方法&#xff0c…

【SSM】医疗健康平台-管理端-运营数据报表导出

知识目标 熟悉JasperReports的用法&#xff0c;能够使用JasperReports实现PDF文件导出 掌握Excel方式导出运营数据报表的方法&#xff0c;能够使用Apache POI以Excel方式导出运营数据报表 掌握PDF方式导出运营数据报表的方法&#xff0c;能够使用JasperReports以PDF方式导出运…

如何快速解决验证码图像问题 | 最佳图像(OCR)验证码解决工具

你是否曾经遇到过陷入一个看似无尽的 CAPTCHA 挑战中&#xff0c;努力识别扭曲的字符或数字&#xff1f;这些令人抓狂的 CAPTCHA 是为了确保你是人类而不是机器人&#xff0c;但它们也给真正的用户带来了头痛。那么&#xff0c;有没有快速解决这些 CAPTCHA 图像的方法&#xff…

SiLM59xx系列SiLM5991SHCG-DG 带有主动保护和高 CMTI 的单通道隔离门极驱动芯片

SiLM59xx系列SiLM5991SHCG-DG是一款单通道隔离驱动器&#xff0c;提供12A源电流和12A灌电流。主动保护功能包括退饱和过流检测、UVLO、隔离故障报警和 4A 米勒钳位。输入侧电源的工作电压为3V至5.5V&#xff0c;输出侧电源的工作电压范围为13V至30V。所有电源电压引脚都有欠压锁…

多车自动驾驶编队与协同控制引领智能物流革命

多车自动驾驶编队与协同控制引领智能物流革命 随着科技的不断进步&#xff0c;智能物流正以前所未有的速度和效率改变着我们的生活和工作方式。在这个领域的最前沿&#xff0c;北京渡众机器人科技有限公司的多车自动驾驶编队与协同控制技术正在为物流行业带来革命性的变革。 北…

武汉星起航:引领潮流!中国跨境出口电商展现强劲增长势头

在全球贸易结构深刻变革的当下&#xff0c;中国跨境出口电商行业正以前所未有的活力和创新能力&#xff0c;引领着中国制造业的转型升级。面对国际贸易规则的日益严格和市场需求的持续升级&#xff0c;中国制造业正通过新型营销渠道和技术条件&#xff0c;以更加开放和主动的姿…

音频概念_STFT_窗口函数

短时傅里叶变换 (Short-Time Fourier Transform, STFT) 是一种时频谱转换算法&#xff0c;它通过在时间上移动窗口函数并计算窗口内信号的频谱来获得信号在时间和频率上的信息。填充信号可以确保每个窗口都有足够的数据进行频谱计算&#xff0c;特别是在窗口函数的边缘。 窗口…