位置: IT常识 - 正文

对于HashMap的容量的一些分析(hashmap的使用场景)

编辑:rootadmin
在Java开发中,我们经常会像如下方式以下创建一个HashMap: Map<String, String> map = new HashMap<String, String>(); 但是上面的代码中,我们并没有给HashMap指定容量,那么,这时候一个新创建的HashMap的默认容量是多少呢?为什么 ... 在Java开发中,我们经常会像如下方式以下创建一个HashMap:Map<String, String> map = new HashMap<String, String>();但是上面的代码中,我们并没有给HashMap指定容量,那么,这时候一个新创建的HashMap的默认容量是多少呢?为什么呢?一、什么是容量?在Java中,保存数据有两种比较简单的数据结构:数组和链表。HashMap底层就是数组+链表(jdk1.8后底层是数组+链表+红黑树)。在HashMap中,有两个比较容易混淆的关键字段:size和capacity ,这其中capacity就是Map的容量,而size我们称之为Map中的元素个数。简单打个比方就更容易理解了:HashMap就是一个“桶”,那么容量(capacity)就是这个桶当前最多可以装多少元素--也就是数组的长度,而元素个数(size)表示这个桶已经装了多少元素。可以用以下代码验证一下:Map<String, String> map = new HashMap<String, String>();map.put("hello", "Java");map.put("hell", "Java");map.put("hel", "Java");map.put("he", "Java");/** 通过反射获取*/Class<?> mapType = map.getClass();Method capacity = mapType.getDeclaredMethod("capacity");capacity.setAccessible(true);System.out.println("capacity : " + capacity.invoke(map));Field size = mapType.getDeclaredField("size");size.setAccessible(true);System.out.println("size : " + size.get(map));通过上面的代码,我们发现了,当我们创建一个HashMap的时候,如果没有指定其容量,那么会得到一个默认容量为16的Map,那么,这个容量是怎么来的呢?又为什么是这个数字呢?实际上这个数字与hash有一定的关系,下面我们看一下hash。二、什么是hash?我们知道,容量就是一个HashMap中”桶”的个数,那么,当我们想要往一个HashMap中put一个元素的时候,需要通过一定的算法计算出应该把他放到哪个桶中,这个过程就叫做hash。hash的功能是根据key来确定这个key-value对在链表数组中的下标的。那么这个数组下标该怎么确定呢?其实简单,我们只要在hash方法中调用Object中的hashCode方法得到一个hash值,然后用这个值对HashMap的容量进行取模就可以得到数组下标了。如果真的是这么简单的话,那HashMap的容量设置也就很简单,但是考虑到效率等问题,HashMap的hash实现被设计的比较复杂,这也造成了HashMap的容量设置有一定限制。下面我们看一下hash的实现。三、hash的实现hash的具体实现上,由两个方法int hash(Object k)和int indexFor(int h, int length)(在jdk1.8后没有这个方法了)来实现。int hash(Object k):该方法主要是将Object转换成一个整型数。int indexFor(int h, int length):该方法主要是将hash()生成的整型数转换成链表数组中的下标。我们先来看下源码:static int indexFor(int h, int length) {return h & (length-1);}在JDK8中无indexFor方法,变为以下源码中的i=(n-1)&hashhash方法中通过(h = k.hashCode()) ^ (h >>> 16)得到hash值indexFor方法通过h & (length-1)得到下标。其中的两个参数h表示元素的hash值,length表示HashMap的容量。i=(n-1)&hash和上面的运算一样。其中的两个参数n表示HashMap的容量,hash表示元素的hash值。那么h & (length-1) 是什么意思呢?其实就是取模。Java之所以使用位运算(&)来代替取模运算(%),最主要的考虑就是效率。位运算(&)效率要比代替取模运算(%)高很多,主要原因是位运算直接对内存数据(二进制)进行操作,不需要转成十进制,因此处理速度非常快。那么,为什么可以使用位运算(&)来实现取模运算(%)呢?实现的原理如下:a%b在当b为2^n时可简化为a&(b-1)证明:1.当b为2^n时,b-1的二进制为011..11(0后面跟n个1).此时a&(b-1)即取a二进制右面n位2.当b为2^n时,a/b的意义就是a右移n位。而右移的n位的值,就是a%b的值理论不好理解,就找几个例子用计算器试一下。6 % 8 = 6 ,6 & 7 = 610 % 8 = 2 ,10 & 7 = 217 % 8 = 1 ,17 & 7 = 1所以,h & (length-1)只要保证length是2^n的话,就可以实现取模运算了。所以,因为位运算要比取模运算的效率更高,所以HashMap在计算元素要存放在数组中的下标的时候,使用位运算代替了取模运算。要实现位运算代替取模运算,要求HashMap的容量一定要是2^n 。那么,既然是2^n ,为啥一定要是16呢?为什么不能是4、8或者32、64等等呢?关于这个默认容量的选择,JDK并没有给出官方解释。这应该就是个经验值,既然一定要设置一个默认的2^n 作为初始值,那么就需要在效率和内存使用上做一个权衡。4、8太小了就有可能频繁发生扩容,扩容就需要重建哈希表,影响效率。32、64等等太大了又浪费空间,不划算。所以,16就作为一个经验值被采用了。在JDK 8中,关于默认容量的定义如上源码 ,其故意把16写成1<<4,就是提醒开发者,这个地方要是2的幂。注释中的 aka 16 也是jdk1.8中新增的,Also Known As 16 -- 又名16既然我们需要把容量设置为2^n,那么HashMap是如何保证其容量一定可以是2^n的呢?要做到这一类型容量,HashMap在指定容量初始化时以及自动扩容时都做了处理。四、指定容量初始化以下是《Java开发手册》中的一段话:可以看到指定容量初始化是被推荐的。当我们通过HashMap(int initialCapacity)这个构造方法设置初始容量的时候,HashMap并不一定会直接采用我们传入的initialCapacity,而是经过计算,得到一个新initialCapacity(例如3变为4,9变为16)。以下为初始化的源码:可以看到最底层就是用tableSizeFor方法得到最终的初始化容量。可以把代码分为Part 1:int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;和Part 2:return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;Part 2比较简单,就是做一下判断,然后把Part 1得到的数值+1。Part 1怎么理解呢?其实是对一个二进制数依次无符号右移,然后与原值取或。拿一个二进制数,套一遍上面的公式就发现其目的:1100 1100 1100 >>>1 = 0110 0110 01101100 1100 1100 | 0110 0110 0110 = 1110 1110 11101110 1110 1110 >>>2 = 0011 1011 10111110 1110 1110 | 0011 1011 1011 = 1111 1111 11111111 1111 1111 >>>4 = 1111 1111 11111111 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的幂。可以用程序试验一下:int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;System.out.println((n < 0) ? 1 : (n >= 1 << 30) ? 1 << 30 : n + 1); 五、自动扩容指定容量初始化后,往集合中put元素时,元素个数超过阈值后怎么办呢?这时候就需要扩容了。HashMap有扩容机制,就是当达到扩容条件时会进行扩容。扩容条件就是当HashMap中的元素个数(size)超过阈值(threshold)时就会自动扩容,通过resize方法进行扩容。threshold = loadFactor * capacity。loadFactor是负载因子,默认值为0.75f,设置成0.75有一个好处,那就是0.75正好是3/4,而capacity又是2的幂。所以,两个数的乘积都是整数。对于一个默认的HashMap来说,默认情况下,当其size大于12(16*0.75)时就会触发扩容。下面是HashMap中的resize方法中的一段源码:newCap = oldCap << 1;newThr = oldThr << 1;从上面两行代码可以看出,扩容后的capacity和threshold变为原来的两倍。可见,当HashMap中size>threshold时就会自动扩容,扩容成原容量的2倍,即从16扩容到32、64、128 …阈值也从12到24,48,96...所以,通过保证初始化容量均为2的幂,并且扩容时也是扩容到之前容量的2倍,所以,保证了HashMap的容量永远都是2的幂,从而保证了位运算代替取模运算,从而提升了效率。可以看到初始化之后容量和阈值是一样的,当放入第一个元素后,重新计算阈值,新的阈值=容量X负载因子。可以用程序实验一下:HashMap m = new HashMap(15);Class<?> mapType = m.getClass();Field threshold = mapType.getDeclaredField("threshold");threshold.setAccessible(true);Method capacity = mapType.getDeclaredMethod("capacity");capacity.setAccessible(true);System.out.println("容量:" + capacity.invoke(m) + " 阈值:" + threshold.get(m) + " 元素数量:" + m.size());for (int i = 0; i < 25; i++) {m.put(i, i);System.out.println("容量:" + capacity.invoke(m) + " 阈值:" + threshold.get(m) + " 元素数量:" + m.size());}六、总结HashMap作为一种数据结构,元素在put的过程中需要进行hash运算,目的是计算出该元素存放在hashMap中的具体位置。hash运算的过程其实就是先对key用hash()方法得到一个hash值,再用hash值对Map的容量进行取模得到下标,而JDK的工程师为了提升取模的效率,使用位运算代替了取模运算,这就要求Map的容量一定得是2的幂。为了保证任何情况下Map的容量都是2的幂,HashMap在两个地方都做了限制。首先是,如果用户制定了初始容量,那么HashMap会计算出比该数大的第一个2的幂作为初始容量。另外,在扩容的时候,也是进行成倍的扩容,即16变成32,32变成64。
本文链接地址:https://www.jiuchutong.com/zhishi/313250.html 转载请保留说明!

上一篇:python中input输入为数字(Python中input输入多行文本)

下一篇:python中的chr() 返回字符(python中返回结果为true)

  • 猫咪流浪十年,用尽全身力气终于找到主人,结局并不完美

    猫咪流浪十年,用尽全身力气终于找到主人,结局并不完美

  • word怎么邮件合并(word怎么邮件合并成绩单)

    word怎么邮件合并(word怎么邮件合并成绩单)

  • 抖音商品分享权限在哪里(抖音商品分享权限在哪打开)

    抖音商品分享权限在哪里(抖音商品分享权限在哪打开)

  • 华为nova7se怎么清理运行应用(华为nova7se怎么添加nfc)

    华为nova7se怎么清理运行应用(华为nova7se怎么添加nfc)

  • opporeno如何开空调(oppo 如何开空调)

    opporeno如何开空调(oppo 如何开空调)

  • 拼多多签到金提现后有效期是多久(拼多多签到金提现失败变成1000减50的劵)

    拼多多签到金提现后有效期是多久(拼多多签到金提现失败变成1000减50的劵)

  • 抖音不认证可以置顶作品吗(抖音不认证可以赚钱吗)

    抖音不认证可以置顶作品吗(抖音不认证可以赚钱吗)

  • 金山毒霸怎么卸载不了(金山毒霸怎么卸载不掉win7)

    金山毒霸怎么卸载不了(金山毒霸怎么卸载不掉win7)

  • 网易云扫一扫在哪里iphone(网易云扫一扫在哪里ipad)

    网易云扫一扫在哪里iphone(网易云扫一扫在哪里ipad)

  • vivo视频通话美颜在哪里打开

    vivo视频通话美颜在哪里打开

  • iphone的悬浮球不见了怎么办(苹果悬浮球失灵怎么办)

    iphone的悬浮球不见了怎么办(苹果悬浮球失灵怎么办)

  • mcafee webadvisor可以卸载吗

    mcafee webadvisor可以卸载吗

  • 为什么华为手机插耳机没有声音(为什么华为手机屏幕变成黑白色)

    为什么华为手机插耳机没有声音(为什么华为手机屏幕变成黑白色)

  • amd驱动安装不了(amd驱动安装不了游戏)

    amd驱动安装不了(amd驱动安装不了游戏)

  • 集成测试和系统测试的区别

    集成测试和系统测试的区别

  • qq音乐可以上传翻唱吗(qq音乐可以上传自己的音乐吗)

    qq音乐可以上传翻唱吗(qq音乐可以上传自己的音乐吗)

  • 抖音怎么刷到喜欢的内容(抖音怎么刷到喜欢的作品)

    抖音怎么刷到喜欢的内容(抖音怎么刷到喜欢的作品)

  • 电越充越少是哪里出了问题(电越充越少是什么问题)

    电越充越少是哪里出了问题(电越充越少是什么问题)

  • 安卓有线耳机苹果能用吗(安卓有线耳机苹果14能用吗)

    安卓有线耳机苹果能用吗(安卓有线耳机苹果14能用吗)

  • vivox27系统更新对手机有影响吗(vivox27系统更新在哪里)

    vivox27系统更新对手机有影响吗(vivox27系统更新在哪里)

  • ps路径转换为选区快捷键(ps路径转换为选区怎么弄)

    ps路径转换为选区快捷键(ps路径转换为选区怎么弄)

  • 数据和wifi同时开启费电吗(wifi和数据能一起用吗)

    数据和wifi同时开启费电吗(wifi和数据能一起用吗)

  • 4500a能带多少瓦(4500a是多少功率)

    4500a能带多少瓦(4500a是多少功率)

  • 怎么把百度视频保存到相册(怎么把百度视频里的音乐提取出来)

    怎么把百度视频保存到相册(怎么把百度视频里的音乐提取出来)

  • 华为手机微信怎么美颜(华为手机微信怎么迁移聊天记录到新手机)

    华为手机微信怎么美颜(华为手机微信怎么迁移聊天记录到新手机)

  • 金管家换手机号怎么换(金管家怎么注销登录手机号)

    金管家换手机号怎么换(金管家怎么注销登录手机号)

  • vivox27pro微信带美颜功能吗(vivo手机x27微信没有提示音怎么回事)

    vivox27pro微信带美颜功能吗(vivo手机x27微信没有提示音怎么回事)

  • 小米语言设置在哪里(小米语言设置在哪里打开)

    小米语言设置在哪里(小米语言设置在哪里打开)

  • qq帐号是什么(qq申请账号)

    qq帐号是什么(qq申请账号)

  • 退税现金流量表做哪里
  • 个体工商户要做帐吗
  • 一般纳税人的税点
  • 税财通财务软件备份与恢复
  • 首套房契税税率是多少?
  • 各行业的利润率表
  • 征地费用应计入什么会计科目
  • 端午节福利计入什么科目
  • 代扣个人所得税现金流入哪个科目?
  • 代购货物的缴税情况
  • 提供劳务企业所得税纳税义务发生时间
  • 出纳工人借支与贷款区别
  • 税务风险有哪些
  • 劳动法相关法规
  • 哪些科目需要计提资产减值损失
  • 收到社保补差款怎么办
  • 纳税调减事项有
  • 零退税率可以做免税吗
  • 食堂伙食费怎么入账
  • 进项税是在抵扣吗
  • 一般纳税人如何零申报
  • 非货币性资产交换准则
  • 固定资产和在建工程占所有者权益的占比
  • 软件企业用退税吗
  • 汇算清缴报错了怎么更正
  • 归还法人前期垫付款项
  • 小规模纳税人企业所得税优惠政策最新2023
  • 映泰主板bios设置硬盘启动
  • 认缴出资日期没到
  • vcpkgsrv.exe是什么进程
  • 穿墙路由器怎么选择
  • 已开票未收款怎么做账
  • 低值易耗品是怎样的
  • php关闭报错
  • php生成guid
  • PHP+MySql+jQuery实现的"顶"和"踩"投票功能
  • vuex中this.$store.commit和this.$store.dispatch的用法
  • 什么是大语言模型(LLM)?
  • php 通信
  • 会计打印发票请求怎么写
  • 确认收入与结转成本会计分录怎么写
  • 长期挂账的其他应付款税务风险
  • 资产减值损失属于什么科目借贷方向
  • 为什么盈余公积减少,未分配利润增加
  • 通过点击一个按键的游戏
  • 税控盘的作用是什么
  • 增值税一般纳税人登记管理办法
  • 免税农产品发票需要勾选吗
  • 支出应计入管理费用,而且要根据其发生额
  • 现金流量表为负数的几种原因
  • 个人缴纳公积金的方法
  • 用于餐厅的不锈钢餐具
  • 普惠性幼儿园是什么意思
  • 9个点的税是多少
  • 房屋出租后转租缴纳房产税吗
  • 企业所得税弥补亏损可以弥补几年
  • 企业向个人借款利息如何缴纳增值税
  • 支付给其他公司的借款属于什么现金流
  • 机票的抵扣进项税的注意事项
  • 每月分红会计分录
  • 够拼了 安装Win8.1过程中出现预约Win10升级提示
  • win7怎么打开后缀
  • ubuntu怎么安装程序
  • Win10 Mobile 10572预览版上手体验视频
  • ubuntu20怎么连接蓝牙鼠标
  • win10电脑提示
  • win10系统怎么设置默认打印机
  • unity4.1
  • Unity3D游戏开发pdf
  • python3 ftplib
  • 置顶怎么折叠起来
  • 请问在javascript程序中
  • javascript基础教程答案
  • 用持久的喷剂有副作用吗
  • 一个绿色
  • python设计二叉树结构
  • 消费税增值税的区别与联系
  • 医院的电子收据怎么查
  • 护肤品关税税率
  • 河北航天信息技术有限公司官网
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设