【Java集合篇】为什么HashMap的Cap是2^n,如何保证?

news2024/11/18 14:00:07

在这里插入图片描述

为什么HashMap的Cap是2^n,如何保证?

  • ✔️目录
    • ✔️ 为什么是2 ^ n ?
      • ✔️为什么 X %2^n = X & (2^n - 1) ?
    • ✔️如何保证
    • ✔️初始化时期保证
    • ✔️扩容时期保证


✔️目录


✔️ 为什么是2 ^ n ?


HashMap是通过 (table.length - 1) & (key.hashCode ^ (key.hashCode >> 16)) 定位 tablelndex 的。


为什么是用 & 而不是用 % 呢 ? 因 为 & 是基于内存的二进制直接运算,比转成十进制的取模快的多。又因为 X % 2^n = X & (2n - 1),可以把%运算转换为&运算。所以,hashMapcapacity 一定要是 2^n。这样,HashMap计算hash的速度才够快。


✔️为什么 X %2^n = X & (2^n - 1) ?


假设n为3,则2^3 = 8,表示成2进制就是1000。2^3 -1 = 7,即0111。


此时X &(2^3 - 1)就相当于取X的2进制的最后三位数。


从2进制角度来看,X/8相当于 X >>3,即把X右移3位,此时得到了X/8的商,而被移掉的部分(后二位),则是X % 8,也就是余数。


上面的解释不知道你有没有看懂,没看懂的话其实也没关系,你只需要记住这个技巧就可以了。或者你可以找几个例子试一下。


如: 6%8 = 6,6&7= 6 10 %8 = 2,10&7 = 2


✔️如何保证


要想保证 HashMap 的容量始终是2^n次方,需要在Map初始化的时候,和扩容的时候分别保证。


/**
*    当我们在实现HashMap时,为了保持其容量始终为2的幂,我们可以重写其resize方法,在每次扩容时调整容量为最接近的2的幂。
*/
public class MyHashMap<K, V> extends HashMap<K, V> {  
    private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 16, which is 2^4  
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;  
  
    @Override  
    protected void putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict, Node<K,V> node) {  
        // 在这里添加初始化时的逻辑  
        System.out.println("Putting key: " + key + ", value: " + value);  
        super.putVal(hash, key, value, onlyIfAbsent, evict, node);  
    }  
  
    @Override  
    protected Node<K,V>[] resize() {  
        // 获取当前容量  
        int oldCapacity = this.capacity;  
        // 计算新的容量,为最接近的2的幂  
        int newCapacity = Integer.highestPowerOfTwo(oldCapacity * 2);  
        // 调用父类的resize方法  
        return super.resize();  
    }  
}

✔️初始化时期保证


当我们通过 HashMap(int initialCapacity)设置初始容量的时候,HashMap并不一定会直接采用我们传入的数值,而是经过计算,得到一个新值,目的是提高hash的效率。(1 -> 1、3`-> 4、7 -> 8、9 -> 16)


在JDK 1.7和JDK 1.8中,HashMap初始化这个容量的时机不同。JDK 1.8中,在调用HashMap的构造函数定义HashMap的时候,就会进行容量的设定。而在JDK 1.7中,要等到第一次put操作时才进行这一操作。


看一下JDK是如何找到比传入的指定值大的第一个2的幂的:


static final int tableSizeFor(int cap) {
	int n = cap - 1;
	n |= n >>>1;
	n |= n >>>2;
	n |= n >>>4;
	n |= n >>>8;
	n |= n >>>16;
	n |= n >>>32;
	n |= n >>>64;
	n |= n >>>128;
	n |= n >>>256;
	return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

上面的算法目的挺简单,就是: 根据用户传入的容量值(代码中的cap),通过计算,得到第一个比他大的2的幂并返回。


在这里插入图片描述

请关注上面的几个例子中,标记颜色字体部分的变化情况,或许你会发现些规律。

5->8、9->16、19->32、37->64 都是主要经过了两个阶段


Step 1,5->7
Step 2,7->8
Step 1,9->15
Step 2,15->16
Step 1,19->31
Step 2,31->32


对应到以上代码中,Step1:


	n |= n >>>1;
	n |= n >>>2;
	n |= n >>>4;
	n |= n >>>8;
	n |= n >>>16;

对应到以上代码中,Step2:


return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

Step 2 比较简单,就是做一下极限值的判断,然后把Step 1得到的数值+1。


Step 1 怎么理解呢?其实是对一个二进制数依次无符号右移,然后与原值取或。其目的对于一个数字的二进制,从第一个不为0的位开始,把后面的所有位都设置成1。


因为cap是int类型的,所以最多需要右移16位即可获取其最大值


随便拿一个二进制数,套一遍上面的公式就发现其目的了:
1100 1100 1100 >>>1 = 0110 0110 0110
1100 1100 1100 | 0110 0110 0110 = 1110 1110 1110
1110 1110 1110 >>>2 = 0111 1011 1011
1110 1110 1110 | 0011 1011 1011 = 1111 1111 1111
1111 1111 1111 >>>4 = 1111 1111 1111
1111 1111 1111 |  1111 1111 1111 = 1111 1111 1111

通过几次无符号右移和按位或运算,我们把1100 1100 1100转换成了1111 1111 1111,再把1111 1111 1111加1,就得到了1 0000 0000 0000,这就是大于1100 1100 1100的第一个2的幕。


好了,我们现在解释清楚了Step 1和Step 2的代码。就是可以把一个数转化成第一个比他自身大的2的次幂。


但是还有一种特殊情况套用以上公式不行,这些数字就是2的幂自身。如果cap=4套用公式的话。得到的会是 8,不过其实这个问题也被解决了,重点就在 int n = cap - 1; 这行代码中,HashMap会事先将用户给定的容量-1,这样就不会出现上述问题了。


总之,HashMap根据用户传入的初始化容量,利用无符号右移和按位或运算等方式计算出第一个大于该数的2的幕。


✔️扩容时期保证


除了初始化的时候会指定HashMap的容量,在进行扩容的时候,其容量也可能会改变。


HashMap有扩容机制,就是当达到扩容条件时会进行扩容。HashMap的扩容条件就是当HashMap中的元素个数 (size) 超过临界值 (threshold) 时就会自动扩容。


在HashMap中, threshold = loadFactor * capacity


loadFactor是装载因子,表示HashMap满的程度,默认值为0.75f,设置成0.75有一个好处,那就是0.75正好是3/4,而 capacity 又是2的幕。所以,两个数的乘积都是整数。


对于一个默认的HashMap来说,默认情况下,当其size大于12(16*0.75)时就会触发扩容。


下面是HashMap中的扩容方法(resize)中的一段:
if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) {
	newThr = oldThr << 1: // double threshold
}

因为oldThr已经是2^n了, 所以oldThr无符号左移之后,是oldThr*2,自然也是2^n。至于后续HashMap是如何扩容的,请听下回分解~

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

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

相关文章

swing快速入门(四十)JList、JComboBox实现列表框

注释很详细&#xff0c;直接上代码 新增内容 &#x1f9e7;1.列表的属性设置与选项监听器 &#x1f9e7;2.下拉框的属性设置与选项监听器 &#x1f9e7;3.Box中组件填充情况不符合预期的处理方法 &#x1f9e7;4.LIst向Vector的转化方法 源码&#xff1a; package swing31_40;i…

Unity中Shader序列帧动画(总结篇)

文章目录 前言一、半透明混合自定义调整1、属性面板2、SubShader中3、在片元着色器(可选)3、根据纹理情况自己调节 二、适配Build In Render Pipeline三、最终代码 前言 在前几篇文章中&#xff0c;我们依次解决了实现Shader序列帧动画所遇到的问题。 Unity中Shader序列图动画…

K8S集群部署解决工作节点couldn‘t get current server API group list问题

最近在自己电脑上装了VMWare Player&#xff0c;在上面装了两个Ubuntu虚拟机&#xff0c;为了方便学习云原生技术&#xff0c;决定在上面装一个2个节点&#xff08;一个控制面&#xff0c;一个工作节点&#xff09;的K8S集群。 参考这篇文章&#xff1a; Ubuntu 22.04 搭建K8…

WebStorm 创建一个Vue项目

一、下载并安装WebStorm 步骤一 步骤二 选择激活方式 激活码&#xff1a; I2A0QUY8VU-eyJsaWNlbnNlSWQiOiJJMkEwUVVZOFZVIiwibGljZW5zZWVOYW1lIjoiVU5JVkVSU0lEQURFIEVTVEFEVUFMIERFIENBTVBJTkFTIiwiYXNzaWduZWVOYW1lIjoiVGFvYmFv77yaSkVU5YWo5a625qG25rAIOa0uW3peS9nOWup…

MATLAB指令

01--根据数学公式进行绘制 1.绘制连续函数 ①一元函数 t0:0.1:10; y3*t2; plot(t,y) ②一元二次函数 t0:0.1:10; yt.*t; plot(t,y) 注意此处应为点乘 ③一元3次 t0:0.1:10; yt.*t.*t; plot(t,y) ④y1/t t0:0.1:10; y1./t; plot(t,y) ⑤yexp(t) t0:0.1:10; yexp(2*t); p…

实现pytorch版的mobileNetV1

mobileNet具体细节&#xff0c;在前面已做了分析记录&#xff1a;轻量化网络-MobileNet系列-CSDN博客 这里是根据网络结构&#xff0c;搭建模型&#xff0c;用于图像分类任务。 1. 网络结构和基本组件 2. 搭建组件 &#xff08;1&#xff09;普通的卷积组件&#xff1a;CBL …

光明源:智慧公厕在实际应用中作用

什么是智慧公厕呢&#xff1f; 智慧公厕是一种应用先进科技和智能化技术的公共卫生设施&#xff0c;旨在提高公厕的管理效率、服务水平以及用户体验。这类公厕整合了各种现代技术&#xff0c;包括实时监控系统、智能预约服务、在线反馈机制、卫生自动化技术、导航服务、电子支…

Git 常用命令详解及如何在IDEA中操作

文章目录 前言发现宝藏一、初识Git1.Git概述2. Git的功能3. Git运行图示 二、Git下载安装三、Git 代码托管服务1.常用的 Git 代码托管服务2.使用码云代码托管服务 四、Git 常用命令1.Git 全局设置2.获取Git 仓库3.工作区、暂存区、版本库 概念4.Git 工作区中文件的两种状态5.本…

视频云存储/视频智能分析平台EasyCVR在麒麟系统中无法启动该如何解决?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

2024年阿里云、腾讯云、华为云、LightNode、硅云服务器如何选?怎么买最划算?[最新价格表]

很多小伙伴都有一颗上云的心&#xff0c;包括我自己 有事没事的折腾一下自己的小破站&#xff0c;也挺有意思的&#xff01; 那么&#xff0c;云服务器哪家好&#xff1f;优惠力度哪家大&#xff1f;活动入口哪里进&#xff1f;云服务器如何配置&#xff1f;如何选型&#xf…

时间序列预测 — VMD-LSTM实现单变量多步光伏预测(Tensorflow):单变量转为多变量预测多变量

目录 1 数据处理 1.1 导入库文件 1.2 导入数据集 ​1.3 缺失值分析 2 VMD经验模态分解 2.1 VMD分解实验 2.2 VMD-LSTM预测思路 3 构造训练数据 4 LSTM模型训练 5 LSTM模型预测 5.1 分量预测 5.2 可视化 时间序列预测专栏链接&#xff1a;https://blog.csdn.net/qq_…

前端Web系统架构设计

文章目录 1.目录结构定义2. 路由封装2.1 API路由定义2.2 组件路由定义 3. Axios请求开发4. 环境变量封装5. storage模块封装(sessionStorage, localStorage)6. 公共函数封装(日期,金额,权限..)7. 通用交互定义(删除二次确认,类别,面包屑...)8. 接口全貌概览 1.目录结构定义 2. …

Flume基础知识(十):Flume 聚合实战

1&#xff09;案例需求&#xff1a; hadoop100上的 Flume-1 监控文件/opt/module/group.log&#xff0c; hadoop101上的 Flume-2 监控某一个端口的数据流&#xff0c; Flume-1 与 Flume-2 将数据发送给 hadoop102 上的 Flume-3&#xff0c;Flume-3 将最终数据打印 到控制台。…

基于Java实现全功能电子商城

&#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩项目推荐订阅&#x1f447;&#x1f3fb; 不然下次找不到哟 基于SpringBoot的旅游网站 基于SpringBoot的MusiQ音乐网站 感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及…

【数据库】视图索引执行计划多表查询面试题

文章目录 一、视图1.1 概念1.2 视图与数据表的区别1.3 优点1.4 语法1.5 实例 二、索引2.1 什么是索引2.2.为什么要使用索引2.3 优缺点2.4 何时不使用索引2.5 索引何时失效2.6 索引分类2.6.1.普通索引2.6.2.唯一索引2.6.3.主键索引2.6.4.组合索引2.6.5.全文索引 三、执行计划3.1…

2024.1.5 关于 二叉平衡树(AVL 树)详解

目录 二叉搜索树 二叉搜索树的简介 二叉搜索树的查找 二叉搜索树的效率 AVL树 AVL 树的简介 AVL 树的实现 AVL树的旋转 右单旋 左单旋 左右双旋 右左双旋 完整 AVL树插入代码 验证 AVL 树 AVL 树的性能 二叉搜索树 要想了解关于二叉平衡树的相关知识&#xff0c;了…

mnn-llm: 大语言模型端侧CPU推理优化

在大语言模型(LLM)端侧部署上&#xff0c;基于 MNN 实现的 mnn-llm 项目已经展现出业界领先的性能&#xff0c;特别是在 ARM 架构的 CPU 上。目前利用 mnn-llm 的推理能力&#xff0c;qwen-1.8b在mnn-llm的驱动下能够在移动端达到端侧实时会话的能力&#xff0c;能够在较低内存…

安全与认证Week3

目录 Key Management 密钥管理 密钥交换、证书 密钥的类别 密钥管理方面 密钥分发问题 密钥分发方案 混合密钥分发 公钥分发 公钥证书 X.509 理解X.509 X.509证书包含 X.509使用过程 X.509身份验证服务 X.509版本3 取消 由X.509引申关于CA 用户认证、身份管理…

手机上下载 Linux 系统

我们首先要下载 Ternux 点击下载以及vnc viewer (提取码&#xff1a;d9sX)&#xff0c;需要魔法才行 下载完以后我们打开 Ternux 敲第一个命令 pkg upgrade 这个命令是用来跟新软件的 敲完命令就直接回车&#xff0c;如果遇到需要输入 Y/N 的地方全部输入 Y 下一步 #启动TMOE…

HackTheBox - Medium - Linux - Ambassador

Ambassador Ambassador 是一台中等难度的 Linux 机器&#xff0c;用于解决硬编码的明文凭据留在旧版本代码中的问题。首先&#xff0c;“Grafana”CVE &#xff08;“CVE-2021-43798”&#xff09; 用于读取目标上的任意文件。在研究了服务的常见配置方式后&#xff0c;将在其…