简述HashMap的扩容机制

news2024/11/28 6:31:55

注意:本博客需要对HashMap源码有过一定理解,看过源码比较好,仅供互相学习参考

JDK1.7和JDK1.8对比

1.7版本:

  • (1). 首先生成一个新数组
  • (2). 遍历老数组每个位置中的链表元素
  • (3). 取每个元素的key,重新计算每个元素在数组的下标
  • (4). 将元素添加到新数组中
  • (5). 所有元素转移完成之后,将新数组赋值给HashMap里的table属性

1.8版本

  • (1). 首先生成一个新数组
  • (2). 遍历老数组每个位置中的链表或者红黑树
  • (3). 如果是链表直接计算每个元素的下标,然后将元素添加到新数组
  • (4). 如果是红黑树,则先遍历红黑树,计算出红黑树每个元素在新数组的下标位置
    • a. 统计每个数组下标位置的元素个数
    • b. 如果下标位置元素个数8,则生成一棵新的红黑树,并将树的根节点添加到新数组的对应下标位置
    • c. 如果下标位置元素个数小于8,则生成一个链表,并将链表的头节点添加到新数组的对应位置
  • (5). 所有的元素转移完成之后,将新数组赋值给HashMap里的table属性

这个知识点是HashMap中的一个重点之一,也是一个比较难的问题

什么时候需要扩容?

当hashMap中元素个数超过thresholdthreshold 为数组长度乘以负载因子loadFactor,loadFactor默认是0.75f

什么是HashMap的扩容?

resize这个方法是HashMap的扩容方法,是比较耗时的。HashMap在扩容时,都是翻两倍,比如16的容量扩大到32,。HashMap进行扩容的方法是比较巧妙的,扩容后,与原来的下标(n-1)&hash相对,其实只是多了1bit位。扩容后节点要么是在原来位置,听起来好像很懵,所以还是认真看下面的分析:

下面给出例子,比如从容量为16扩容到32时,画图表示:
在这里插入图片描述
进行扩容,扩大到原来的两倍:
在这里插入图片描述
到这一步,下标(n-1) & hash,扩容后的数据10101和原来的00101相比,其实就是多了1bit,10101是十进制的21,而21=5+16,就是“原位置+旧容量”,还有另外一种情况是保持为0的情况,这种情况是不改变位置的

下面给出一份表格,数据如图:
在这里插入图片描述
容量为16的情况
在这里插入图片描述
有低位的两个指针loHead、lloTail,高位的两个指针hiHead、hiTail
在这里插入图片描述
扩容到32之后,再两个链表加到对应位置。分别有两种情况,保持原来位置的和“原位置+旧容量”这个位置
在这里插入图片描述
所以,扩容的过程,对应的节点位置改变是这样的过程:
在这里插入图片描述

resize的源码实现

经过上面比较详细的分析,这个实现逻辑是可以在代码里找到对应的,ok,跟一下对应的源码:

final Node<K,V>[] resize() {
   // 得到当前的节点数组
    Node<K,V>[] oldTab = table;
    // 数组的长度
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    // 计算扩容后的大小
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) { // 超过最大容量 即 1 <<< 30
             // 超过最大容量就不扩充了,修改阀值为最大容量
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 没超过的情况,扩大为原来的两倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
         // 老阀值赋值给新的数组长度
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        // 使用默认值16
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 重新计算阀值,然后要赋值给threshold
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    // 新的阀值,原来默认是12,现在变为24
    threshold = newThr;
    // 创建新的节点, newCap是新的数组长度,为32
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    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)
                     // 是红黑树节点,调用split方法
                    ((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指向要移动的节点e
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        // 位置不变
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        // hiTail指向null
                        hiTail.next = null;
                        // oldCap是旧容量 ,移动到“原位置+旧容量”这个位置
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

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

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

相关文章

深度学习ai学习方向如何规划,算法竞赛,机器学习,搭建环境等答疑

目录 1了解人工智能的背景知识 2 补充数学或编程知识 3 熟悉机器学习工具库 4 系统的学习人工智能 5 动手去做一些AI应用 1了解人工智能的背景知识 一些虽然存在但是在研究或者工业上不常用的知识&#xff0c;为自己腾出更多的时间来去学习&#xff0c;研究。 人工智能里…

2023.7.16-约数的枚举

功能&#xff1a;输入一个整数&#xff0c;结果打印出这个整数所有的约数。 程序&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() {int a0, b;printf("请输入一个整数&#xff1a;");scanf("%d",&a);printf(&qu…

迭代器模式:相比直接遍历集合数据,使用迭代器有哪些优势?

今天&#xff0c;我们学习另外一种行为型设计模式&#xff0c;迭代器模式。它用来遍历集合对象。不过&#xff0c;很多编程语言都将迭代器作为一个基础的类库&#xff0c;直接提供出来了。在平时开发中&#xff0c;特别是业务开发&#xff0c;我们直接使用即可&#xff0c;很少…

前端基础:HTML和CSS简介

目录 1、HTML 简介 &#xff08;1&#xff09;在 HTML 中引入外部 CSS &#xff08;2&#xff09;在 HTML 中引入外部 JavaScript 2、CSS 简介 &#xff08;1&#xff09;CSS 的基本语法 &#xff08;2&#xff09;三种使用 CSS 的方法 2.1 - 外部 CSS 的使用 2.2 - 内…

Redis简介与安装

文章目录 前言一、Redis简介1. Redis是什么2. Redis的特点3. 数据库类型4. Redis 应用场景 二、Redis下载与安装1. Redis安装包下载地址2. 在 windows系统安装 Redis3. 在Linux系统安装Redis 总结 前言 为了巩固所学的知识&#xff0c;作者尝试着开始发布一些学习笔记类的博客…

面向对象习题

创建类Calc,定义2个数属性以及一个符号属性,编写4个方法add、minus、multiply、divide,4个方法分别进行2个小数的加、减、乘、除运算.在主函数里面接受用户输入2个运算数、1个运算符,根据该运算符选择应该调用哪一个方法进行运算。 定义10名学生&#xff0c;循环接收10名学员的…

【Kubernetes运维篇】RBAC认证授权详解(二)

文章目录 一、RBAC认证授权策略1、Role角色2、ClusterRole集群角色3、RoleBinding角色绑定和ClusterRoleBinding集群角色绑定 二、通过API接口授权访问K8S资源三、案例&#xff1a;常见授权策略1、常见的角色授权策略案例2、常见的角色绑定案例3、常见的ServiceAccount授权绑定…

WIN10更改代理设置后无法保存的解决办法

每次更改代理之后保存&#xff0c;推出界面再进来发现还是和原来一样 这应该是代理报错失败解决办法如下 winR&#xff0c;regedit&#xff0c;打开注册表编辑器 找到计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings里面的Pr…

一例flash打包的文件夹病毒的分析

今天分析一例样本&#xff0c;该样本使用flask编写&#xff0c;使用MDM Zinc3打包成exe&#xff0c;使用文件夹图标&#xff0c;会在系统中除了C盘外所有驱动器根目录创建photo目录&#xff0c;将自身拷贝进去&#xff0c;诱导用户点击&#xff0c;会添加开机启动项&#xff0c…

03插值与拟合

9.已知飞机下轮廓线上数据如下&#xff0c;分别用分段线性插值和三次样条插值求x每改变0.1时的y值。 x035791112131415y01.21.72.02.12.01.81.21.01.6 %9.已知飞机下轮廓线上数据如下&#xff0c;分别用分段线性插值和三次样条插值求每改变0.1时的y值。x [0 3 5 7 9 11 12 1…

浮点数的存储

❤️ 作者简介 &#xff1a;对纯音乐情有独钟的阿甘 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识&#xff0c;对纯音乐有独特的喜爱 &#x1f4d7; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;如果你也感兴趣的话欢迎关注博主&#xff0c;期待更新 1. 浮点型…

伙伴来做客,文心千帆大模型平台实操秘籍大公开

7月15日&#xff0c;业界首个大模型实训营——百度智能云文心千帆大模型平台实训营在百度大厦举办。百度智能云携手软通动力、中科软、科蓝、中软国际、天源迪科、世纪互联、宝兰德等14家合作伙伴的25位CTO和技术总监&#xff0c;为伙伴在实际落地中更好地应用大模型技术提供支…

【图像处理】Python判断一张图像是否亮度过低,图片模糊判定

文章目录 亮度判断模糊判断 亮度判断 比如&#xff1a; 直方图&#xff1a; 代码&#xff1a; 这段代码是一个用于判断图像亮度是否过暗的函数is_dark&#xff0c;并对输入的图像进行可视化直方图展示。 首先&#xff0c;通过import语句导入了cv2和matplotlib.pyplot模块…

C语言---数据结构实验---顺序表的合并---链表的基本操作---重点解析约瑟夫问题

文章目录 顺序表的合并代码实现代码下载 链表的基本操作代码实现代码下载 约瑟夫问题问题分析代码实现 本篇展示数据结构的两个实验 其中&#xff0c;重点分析约瑟夫问题 实验中代码的命名风格等均与下方博客风格类似&#xff0c;全程手撕图解 对顺序表和链表不清楚有以下文…

平衡小车学习教程2(软件篇)——MPU6050数据读取欧拉角,移植DMP读取Roll角、Pitch角、Yaw角

前言 上一篇&#xff0c;给大家介绍了平衡小车的硬件资源及其小车底层硬件介绍篇 平衡小车学习教程1——硬件资源及其小车底层硬件介绍篇 这篇来教大家如何快速的使用MPU6050自带的DMP库读取陀螺仪数据&#xff0c;读取翻滚角(Roll)、俯仰角(Pitch)、航向角(Yaw)这三个角的数…

卖家如何做好独立站?这些要点要牢记!

随着跨境电商第三方平台的竞争压力越来越大&#xff0c;以及平台流量红利期迎来大幅缩水&#xff0c;使得越来越多的跨境卖家开始另寻出路&#xff0c;建设自己的品牌。而独立站的优势特点&#xff0c;在当下的市场竞争环境中优势更为突出&#xff0c;这也使得众多卖家涌入独立…

05-MySQL-基础篇-SQL之DQL语句

SQL之DQL语句 前言DQL准备数据基础语法基本查询条件查询集合函数分组查询排序查询分页查询执行顺序 前言 本篇来学习下SQL中的DQL语句 DQL 英文全称是Data Query Language(数据查询语言)&#xff0c;数据查询语言&#xff0c;用来查询数据库中表的记录。 准备数据 # 删除表…

Spring框架概述及核心设计思想

文章目录 一. Spring框架概述1. 什么是Spring框架2. 为什么要学习框架&#xff1f;3. Spring框架学习的难点 二. Spring核心设计思想1. 容器是什么&#xff1f;2. IoC是什么&#xff1f;3. Spring是IoC容器4. DI&#xff08;依赖注入&#xff09; 一. Spring框架概述 1. 什么是…

芜湖,埋点还可以这么做?这也太简单了

目录 前言 一个埋点的Demo 安装依赖 添加测试代码 编写入口文件 编写插件 运行Demo 处理_tracker的import 改进 给其他的函数类型添加埋点 处理埋点函数变量名 总结&#xff1a; 前言 在项目开发中通常会有埋点的需求&#xff0c;然而当项目过于庞大&#xff0c;给…

聚焦型光场相机基于立体视差的深度估计原理

聚焦型光场相机可以看作是主透镜将物面成了一个放大或者缩小的虚像&#xff0c;然后每个微透镜阵列对这个经过放大或者缩小的虚像进行二次成像后投影在了ccd平面&#xff0c;其中二次成像的过程可以比拟为一个虚拟阵列相机&#xff0c;利用MLA和主透镜的相关参数就可以以立体视…