JDK1.8中HashMap的resize()方法详解

news2024/9/26 1:21:54

JDK1.8中HashMap的resize()方法详解

文章目录

  • JDK1.8中HashMap的resize()方法详解
    • @[toc]
    • 一、概述
    • 二、源码解析
    • 三、元素迁移
    • 四、小结

在学习本文之前,默认大家已经有了HashMap源码的前置知识。

「集合底层」深入浅出HashMap底层源码

一、概述

resize()方法的代码比较长,我们先用文字来总结一下该方法具体做了什么。

  • 首先记录当前数组信息,当前数组、数组长度还有扩容阈值。
  • 接着就到了一个if-elseif-else的代码块,这些就是用来判断当前是进行初始化操作还是扩容操作,如果是扩容操作则需要进行双倍扩容,如果是初始化数组则需要设置数组容量。
  • 如果是扩容操作或者初始化的时候用户指定了初始容量,则要⽤新数组的大小重写计算扩容阈值
  • 重新生成一个数组(无论是扩容操作还是初始化操作都需要)。
  • 如果是初始化操作,到生成数组就已经结束了,但如果是扩容操作,则把⽼数组上的元素转移到新数组上

简单总结:判断当前数组是初始化还是扩容,初始化就根据情况设置数组长度并创建数组;如果是扩容,需要双倍扩容并转移上面的元素。


二、源码解析

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    //这里的oldCap等于null对应了前面hashmap介绍时,put第一个元素resize的场景
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    // 1.老表的容量不为0,即老表不为空
    if (oldCap > 0) {
        // 1.1 判断老表的容量是否超过最大容量值:如果超过则将阈值设置为Integer.MAX_VALUE,并直接返回老表,
        // 此时oldCap * 2比Integer.MAX_VALUE大,因此无法进行重新分布,只是单纯的将阈值扩容到最大
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 1.2 将newCap赋值为oldCap的2倍,如果newCap<最大容量并且oldCap>=16, 则将新阈值设置为原来的两倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    // 2.如果老表的容量为0, 老表的阈值大于0, 是因为初始容量被放入阈值,则将新表的容量设置为老表的阈值
    else if (oldThr > 0)
        newCap = oldThr;
    else {
        // 3.老表的容量为0, 老表的阈值为0,这种情况是没有传初始容量的new方法创建的空表,将阈值和容量设置为默认值
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 4.如果新表的阈值为0, 则通过新的容量*负载因子获得阈值
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    // 5.将当前阈值设置为刚计算出来的新的阈值,定义新表,容量为刚计算出来的新容量,将table设置为新定义的表。
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    // 6.如果老表不为空,则需遍历所有节点,将节点赋值给新表
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {  // 将索引值为j的老表头节点赋值给e
                oldTab[j] = null; // 将老表的节点设置为空, 以便垃圾收集器回收空间
                // 7.如果e.next为空, 则代表老表的该位置只有1个节点,计算新表的索引位置, 直接将该节点放在该位置
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                // 8.如果是红黑树节点,则进行红黑树的重hash分布(跟链表的hash分布基本相同)
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    // 9.如果是普通的链表节点,则进行普通的重hash分布
                    Node<K,V> loHead = null, loTail = null; // 存储索引位置为:“原索引位置”的节点
                    Node<K,V> hiHead = null, hiTail = null; // 存储索引位置为:“原索引位置+oldCap”的节点
                    Node<K,V> next;
                    do {
                        next = e.next;
                        // 9.1 如果e的hash值与老表的容量进行与运算为0,则扩容后的索引位置跟老表的索引位置一样
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null) // 如果loTail为空, 代表该节点为第一个节点
                                loHead = e; // 则将loHead赋值为第一个节点
                            else
                                loTail.next = e;    // 否则将节点添加在loTail后面
                            loTail = e; // 并将loTail赋值为新增的节点
                        }
                        // 9.2 如果e的hash值与老表的容量进行与运算为非0,则扩容后的索引位置为:老表的索引位置+oldCap
                        else {
                            if (hiTail == null) // 如果hiTail为空, 代表该节点为第一个节点
                                hiHead = e; // 则将hiHead赋值为第一个节点
                            else
                                hiTail.next = e;    // 否则将节点添加在hiTail后面
                            hiTail = e; // 并将hiTail赋值为新增的节点
                        }
                    } while ((e = next) != null);
                    // 10.如果loTail不为空(说明老表的数据有分布到新表上“原索引位置”的节点),则将最后一个节点
                    // 的next设为空,并将新表上索引位置为“原索引位置”的节点设置为对应的头节点
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    // 11.如果hiTail不为空(说明老表的数据有分布到新表上“原索引+oldCap位置”的节点),则将最后
                    // 一个节点的next设为空,并将新表上索引位置为“原索引+oldCap”的节点设置为对应的头节点
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    // 12.返回新表
    return newTab;
}



三、元素迁移

JDK1.8则因为巧妙的设计,性能有了大大的提升:由于数组的容量是以2的幂次方扩容的,那么一个Entity在扩容时,新的位置要么在原位置,要么在原长度+原位置的位置。原因如下图:

img

数组长度变为原来的2倍,表现在二进制上就是多了一个高位参与数组下标确定。此时,一个元素通过hash转换坐标的方法计算后,恰好出现一个现象:最高位是0则坐标不变,最高位是1则坐标变为“10000+原坐标”,即“原长度+原坐标”。如下图:

image-20221228184442318

因此,在扩容时,不需要重新计算元素的hash了,只需要判断最高位是1还是0就好了。


四、小结

本章主要解析了JDK8中HashMap的resize方法的底层源码,这个方法主要有两个作用,一是用来初始化数组,二是用来扩容,而扩容的时候就需要转移元素,转移元素又分为四种情况。同时在转移红黑树的时候还提到了一个方法split()方法,在以后的文章中将会解析到。


参考

JDK8中HashMap底层源码解析-resize方法

卓景京|干货集:HashMap 的实现原理

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

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

相关文章

OpenHarmony#深入浅出学习eTs#(四)登陆界面UI

本项目Gitee仓地址&#xff1a;深入浅出eTs学习: 带大家深入浅出学习eTs (gitee.com) 一、明确目标 经过前面两章的学习&#xff0c;大家对Super Visual应该有了一个较为简单的认识&#xff0c;这一章就把前面的知识点串一下&#xff0c;使用Ark UI(Super Visual)赖模仿一个Q…

浅谈权限系统在多利熊业务应用

作者 | 百度智能小程序团队 导读 本文首先引入多利熊业务介绍&#xff0c;引出多利熊业务建设权限系统的痛点&#xff0c;接着分别从权限系统模型、权限系统设计以及多利熊业务业务应用方面详细探讨了具体的方案和设计&#xff0c;最后对权限系统设计思考&#xff0c;对数据维度…

linux连接器脚本前奏-基于x86(一)

从今天开始进入正文,和讲解liteos一样,我们先从连接器脚本开讲。我们知道连接器脚本描述了编译输出程序的布局,那么linux内核编译输出的布局是怎么样的呢?听我慢慢道来,关于连接器脚本的大概使用用途,可以参见 liteos链接器脚本一 liteos链接器脚本二 这里先说明一下对于…

Python进行异步请求,实现多开任务

前言 本文是该专栏的第5篇,后面会持续分享python的各种干货知识,值得关注。 在工作中,你可能或多或少会接到这样一个任务需求。 给你一个任务队列,需要你进行多任务去实现处理,尤其在爬虫项目或者是使用selenium,pyppeteer等任务中比较常见,至于多线程和多进程那些,笔…

OpenCL 是什么

OpenCL 创建Program对象|极客笔记 文章目录 OpenCL标准什么是OpenCL OpenCL全称为Open Computing Language&#xff08;开放计算语言&#xff09;&#xff0c;先由Apple设计&#xff0c;后来交由Khronos Group维护&#xff0c;是异构平台并行编程的开放的标准&#xff0c;也是…

antd 时间类组件的国际化 locale 设置不生效 解决方案汇总

antd 时间类组件的国际化 locale 设置不生效&#xff0c;踩坑之路和解决办法 问题 如图所示&#xff0c;antd 时间类组件中英文混合显示&#xff1a; 初始配置代码如下&#xff1a; import ./index.css; import ./global.less;import { ConfigProvider } from antd; import…

excel表格制作如何设置?新手必备教程!

Excel是一种专门用于制作表格、输入数据和统计分析的办公软件&#xff0c;日常办公中它带给我们极大的便利。下面我们一起来看看excel表格制作如何操作&#xff1f;为了方便理解&#xff0c;下面分为详细的六个步骤。你可以根据下面的操作顺序来操作&#xff08;里面有些顺序是…

Windows版本Tomcat升级openssl版本

本次教程适用于windows版本安装Tomcat调整openssl版本。 下载Tomcat Native Tomcat native提供让Tomcat以APR模式运行&#xff0c;APR的全称是Apache Portable Runtime&#xff0c;它是一个高度可移植的库&#xff0c;它是Apache HTTP Server 2.x的核心。APR有许多用途&#…

element ui Form 自定义校验规则,验证手机号

网站快速成型工具 Element&#xff0c;一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库 指南 了解设计指南&#xff0c;帮助产品设计人员搭建逻辑清晰、结构合理且高效易用的产品。 查看详情 组件 使用组件 Demo 快速体验交互细节&#xff1b;使用前端框架…

@开发者:个推小程序消息推送解决方案来了

随着小程序技术和应用场景的不断完善&#xff0c;越来越多的开发者搭建了小程序平台&#xff0c;为用户带来更“轻量”的服务。在小程序用户迅猛增长的同时&#xff0c;开发者对于小程序用户精细化触达的需求也愈加强烈。近日&#xff0c;个推消息推送上线了小程序推送功能&…

Python量化交易05——基于多因子选择和选股策略(随机森林,LGBM)

参考书目:深入浅出Python量化交易实战 在机器学习里面的X叫做特征变量&#xff0c;在统计学里面叫做协变量也叫自变量&#xff0c;在量化投资里面则叫做因子&#xff0c;所谓多因子就是有很多的特征变量。 本次带来的就是多因子模型&#xff0c;并且使用的是机器学习的强大的非…

Linux系统下的组管理和权限管理

Linux系统下的组管理和权限管理 组管理 在linux中的每个用户必须属于一个组&#xff0c;不能独立于组外。在linux中每个文件有所有者、所在组、其它组的概念。 对于一个文件而言&#xff0c;有以下几种说法&#xff1a;1)所有者&#xff1b;2)所在组&#xff1b;3)其它组&#…

TypeScript中的类 Class

公共属性的修饰符&#xff1a; public&#xff1a;公共&#xff0c;默认修饰符&#xff0c;外部和内部都能使用private&#xff1a;私有的&#xff0c;只能内部类用&#xff0c;外部不能读写protected&#xff1a;当前类和派生类(子类)可访问readonly:外部只能读不能写static&…

快速上手 Docker 最新 WebAssembly 技术预览版

本文为译文&#xff0c;原文见&#xff1a;https://nigelpoulton.com/getting-started-with-docker-and-wasm/ 轻松体验 Docker 和 Wasm ——编写一个应用&#xff0c;将其编译为 Wasm&#xff0c;将其打包为 OCI 镜像&#xff0c;将之存储在 Docker Hub 中&#xff0c;使用 Do…

4 JMeter 参数化常用方式

文章目录2.4 JMeter 参数化常用方式2.4.1 用户定义的变量2.4.2 用户参数2.4.3 CSV数据文件设置 CSV Data Set Config2.4.4 函数(_counter)2.4 JMeter 参数化常用方式 2.4.1 用户定义的变量 应用场景&#xff1a;全局参数 添加方式&#xff1a; 测试计划->线程组->配置…

【AcWing每日一题】4366. 上课睡觉

有 N 堆石子&#xff0c;每堆的石子数量分别为 a1,a2,…,aN。 你可以对石子堆进行合并操作&#xff0c;将两个相邻的石子堆合并为一个石子堆&#xff0c;例如&#xff0c;如果 a[1,2,3,4,5]&#xff0c;合并第 2,3 堆石子&#xff0c;则石子堆集合变为 a[1,5,4,5]。 我们希望…

LeetCodeday02

977.有序数组的平方 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 示例 1&#xff1a; 输入&#xff1a;nums [-4,-1,0,3,10] 输出&#xff1a;[0,1,9,16,100] 解释&#xff1a;平方后&am…

Java开发学习(三十五)----SpringBoot快速入门及起步依赖解析

一、SpringBoot简介 SpringBoot 是由 Pivotal 团队提供的全新框架&#xff0c;其设计目的是用来简化 Spring 应用的初始搭建以及开发过程。 使用了 Spring 框架后已经简化了我们的开发。而 SpringBoot 又是对 Spring 开发进行简化的&#xff0c;可想而知 SpringBoot 使用的简…

做报表要用什么插件?

Excel 作为大家最熟悉的报表工具&#xff0c;很多表哥表姐每天都在使用&#xff0c;为了加强 Excel 的报表功能&#xff0c;市面上有非常多的 Excel 增强插件&#xff0c;为 Excel 增加了千奇百怪的能力。今天给大家介绍一款专门用来做中国式复杂报表的Excel 插件&#xff1a;思…

【不一样的递归大法】

&#x1f381;递归&#x1f385;递归&#x1f98c;定义&#x1f385;何时用递归&#xff1a;递归三板斧&#x1f98c;递归递归&#x1f98c;递归大法&#xff1a;三板斧&#x1f385;如何快速写出递归函数&#xff1a;宏观的角度&#x1f385;解题突破&#x1f98c;整数序列相关…