HashMap中傻傻分不清楚的那些概念

转载自 HashMap中傻傻分不清楚的那些概念

很多人在通过阅读源码的方式学习Java,这是个很好的方式。而JDK的源码自然是首选。在JDK的众多类中,我觉得HashMap及其相关的类是设计的比较好的。很多人读过HashMap的代码,不知道你们有没有和我一样,觉得HashMap中关于容量相关的参数定义的太多了,傻傻分不清楚。


其实,这篇文章介绍的内容比较简单,只要认真的看看HashMap的原理还是可以理解的,单独写一篇文章的原因是因为我后面还有几篇关于HashMap源码分析的文章,这些概念不熟悉的话阅读后面的文章会很吃力。

HashMap中重要的成员变量

先来看一下,HashMap中都定义了哪些成员变量。


上面是一张HashMap中主要的成员变量的图,其中有一个是我们本文主要关注的: sizeloadFactorthresholdDEFAULT_LOAD_FACTORDEFAULT_INITIAL_CAPACITY

我们先来简单解释一下这些参数的含义,然后再分析他们的作用。

HashMap类中有以下主要成员变量:

  • transient int size; 

    • 记录了Map中KV对的个数

  • loadFactor 

    • 装载印子,用来衡量HashMap满的程度。loadFactor的默认值为0.75f(static final float DEFAULT_LOAD_FACTOR = 0.75f;)。

  • int threshold; 

    • 临界值,当实际KV个数超过threshold时,HashMap会将容量扩容,threshold=容量*加载因子

  • 除了以上这些重要成员变量外,HashMap中还有一个和他们紧密相关的概念:capacity 

    • 容量,如果不指定,默认容量是16(static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;)

可能看完了你还是有点蒙,size和capacity之间有啥关系?为啥要定义这两个变量。loadFactor和threshold又是干啥的?

size 和 capacity

HashMap中的size和capacity之间的区别其实解释起来也挺简单的。我们知道,HashMap就像一个“桶”,那么capacity就是这个桶“当前”最多可以装多少元素,而size表示这个桶已经装了多少元素。来看下以下代码:

Map<String, String> map = new HashMap<String, String>();
map.put("hollis", "hollischuang");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,并想其中put了一个元素,然后通过反射的方式打印capacity和size。输出结果为:capacity : 16、size : 1

默认情况下,一个HashMap的容量(capacity)是16,设计成16的好处我在《全网把Map中的hash()分析的最透彻的文章,别无二家。》中也简单介绍过,主要是可以使用按位与替代取模来提升hash的效率。

为什么我刚刚说capacity就是这个桶“当前”最多可以装多少元素呢?当前怎么理解呢。其实,HashMap是具有扩容机制的。在一个HashMap第一次初始化的时候,默认情况下他的容量是16,当达到扩容条件的时候,就需要进行扩容了,会从16扩容成32。

我们知道,HashMap的重载的构造函数中,有一个是支持传入initialCapacity的,那么我们尝试着设置一下,看结果如何。

Map<String, String> map = new HashMap<String, String>(1);
map.put("hahaha", "hollischuang");Class<?> mapType = map.getClass();
Method capacity = mapType.getDeclaredMethod("capacity");
capacity.setAccessible(true);
System.out.println("capacity : " + capacity.invoke(map));Map<String, String> map = new HashMap<String, String>(7);
map.put("hahaha", "hollischuang");Class<?> mapType = map.getClass();
Method capacity = mapType.getDeclaredMethod("capacity");
capacity.setAccessible(true);
System.out.println("capacity : " + capacity.invoke(map));Map<String, String> map = new HashMap<String, String>(9);
map.put("hahaha", "hollischuang");Class<?> mapType = map.getClass();
Method capacity = mapType.getDeclaredMethod("capacity");
capacity.setAccessible(true);
System.out.println("capacity : " + capacity.invoke(map));

分别执行以上3段代码,分别输出:capacity : 2、capacity : 8、capacity : 16

也就是说,默认情况下HashMap的容量是16,但是,如果用户通过构造函数指定了一个数字作为容量,那么Hash会选择大于该数字的第一个2的幂作为容量。(1->2、7->8、9->16)

这里有一个小建议:在初始化HashMap的时候,应该尽量指定其大小。尤其是当你已知map中存放的元素个数时。(《阿里巴巴Java开发规约》)

loadFactor 和 threshold

前面我们提到过,HashMap有扩容机制,就是当达到扩容条件时会进行扩容,从16扩容到32、64、128...

那么,这个扩容条件指的是什么呢?

其实,HashMap的扩容条件就是当HashMap中的元素个数(size)超过临界值(threshold)时就会自动扩容。

在HashMap中,threshold = loadFactor * capacity。

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

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

验证代码如下:

Map<String, String> map = new HashMap<>();
map.put("hollis1", "hollischuang");
map.put("hollis2", "hollischuang");
map.put("hollis3", "hollischuang");
map.put("hollis4", "hollischuang");
map.put("hollis5", "hollischuang");
map.put("hollis6", "hollischuang");
map.put("hollis7", "hollischuang");
map.put("hollis8", "hollischuang");
map.put("hollis9", "hollischuang");
map.put("hollis10", "hollischuang");
map.put("hollis11", "hollischuang");
map.put("hollis12", "hollischuang");
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));Field threshold = mapType.getDeclaredField("threshold");
threshold.setAccessible(true);
System.out.println("threshold : " + threshold.get(map));Field loadFactor = mapType.getDeclaredField("loadFactor");
loadFactor.setAccessible(true);
System.out.println("loadFactor : " + loadFactor.get(map));map.put("hollis13", "hollischuang");
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));Field threshold = mapType.getDeclaredField("threshold");
threshold.setAccessible(true);
System.out.println("threshold : " + threshold.get(map));Field loadFactor = mapType.getDeclaredField("loadFactor");
loadFactor.setAccessible(true);
System.out.println("loadFactor : " + loadFactor.get(map));

输出结果:

capacity : 16
size : 12
threshold : 12
loadFactor : 0.75capacity : 32
size : 13
threshold : 24
loadFactor : 0.75

当HashMap中的元素个数达到13的时候,capacity就从16扩容到32了。

HashMap中还提供了一个支持传入initialCapacity,loadFactor两个参数的方法,来初始化容量和装载因子。不过,一般不建议修改loadFactor的值。

总结

HashMap中size表示当前共有多少个KV对,capacity表示当前HashMap的容量是多少,默认值是16,每次扩容都是成倍的。loadFactor是装载因子,当Map中元素个数超过loadFactor* capacity的值时,会触发扩容。loadFactor* capacity可以用threshold表示。

PS:文中分析基于JDK1.8.0_73


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

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

相关文章

HoloLens开发手记-硬件细节 Hardware Detail

微软HoloLens是世界第一款完全无线缆的全息计算机。通过在新方式上赋予用户的全息体验&#xff0c;HoloLens重新定义了个人计算&#xff08;Personal Computing&#xff09;。为了将3D全息图形固定到你周围的真实世界中&#xff0c;HoloLens融合了最先进的光学元件和传感器。 设…

super构造方法为什么给子类赋值_【Java学习 | Javase】super

整理自&#xff1a;动力节点基础讲义super概述 严格来说&#xff0c;super其实并不是一个引用&#xff0c;它只是一个关键字&#xff0c;super代表了当前对象中从父类继承过来的那部分特征。换句话说&#xff0c;super其实是this的一部分&#xff0c;从父类继承过来的属性和方法…

4-存储器

【README】 1.本文总结自B站 《计算机组成原理&#xff08;哈工大刘宏伟&#xff09;》的视频讲解&#xff0c;非常棒&#xff0c;墙裂推荐&#xff1b; 【1】概述 【1.1】存储器分类 1&#xff09;按存储介质分类 1&#xff0c; 半导体存储器&#xff0c;分为 TTL&#…

坑爹的日志无法按天切割问题

转载自 坑爹的日志无法按天切割问题问题背景 线上某个新管理型系统出现了日志无法按天切割生成日志文件的问题&#xff0c;所有的日志都在一个日志文件里面&#xff0c;只有每次重启的时候才会重新生成文件。 这个管理系统使用的是 Spring Boot Logback 框架&#xff0c;查看了…

Asp.net 面向接口框架之应用程序上下文作用域组件

在团队中推广面向接口开发两年左右,成果总体来说我还是挺满意的,使用面向接口开发的模块使用Unity容器配置的功能非常稳定,便于共享迁移(另一个项目使用只需要复制配置和调用接口即可)也很好扩展(操作的数据库、表、资源等都可以配置)。 但是由于当时开发的匆忙(边开发边应用),…

投票源码程序_[内附完整源码和文档] 基于JSP实现的影视创作论坛系统

摘 要随着时代的发展&#xff0c;互联网的出现&#xff0c;给传统影视行业带来的最大便利就是&#xff0c;方便了影视从业人员以及爱好者的交流和互动&#xff0c;而为用户提供一个书写影评&#xff0c;阅读影评以及回复影评的平台&#xff0c;以影评为载体来使用户感受影评、解…

5-输入输出系统IO

【README】 1.本文总结自B站 《计算机组成原理&#xff08;哈工大刘宏伟&#xff09;》的视频讲解&#xff0c;非常棒&#xff0c;墙裂推荐&#xff1b; 【1】 IO概述 【1.1】输入输出系统的发展概况 通道&#xff08;通道是netty的io多路复用的底层原理&#xff0c;需要重点了…

Java中的基本数据类型转换(自动、强制、提升)

转载自 Java中的基本数据类型转换&#xff08;自动、强制、提升&#xff09; 说基本数据类型转换之前&#xff0c;先了解下 Java 中的 8 种基本数据类型&#xff0c;以及它们的占内存的容量大小和表示的范围&#xff0c;如下图所示。 重新温故了下原始数据类型&#xff0c;现在…

我是这样入侵 Hacking Team 的

在意大利间谍软件厂商 Hacking Team 的内部邮件和文档被曝光将近一年后&#xff0c;黑掉这家黑客公司的黑客公开了他如何入侵HT的完整细节。该文档于上周六在网上发布&#xff0c;本意是为了给黑客活动人士的一份指南。但对于安全从业人员来说&#xff0c;则意味着当任何企业或…

python 数据分析 书籍推荐 知乎_Python 爬取知乎 9674 个问答,揭秘最受欢迎的 98 本书!...

原标题&#xff1a;Python 爬取知乎 9674 个问答&#xff0c;揭秘最受欢迎的 98 本书&#xff01;作者 | Yura责编 | 胡巍巍高尔基这话有没有道理我不知道&#xff0c;咱也不敢问&#xff0c;主要是现在也问不了。那对我来说&#xff0c;读书有什么意义呢&#xff1f;应该也是阶…

分布式作业 Elastic-Job 快速上手指南

转载自 分布式作业 Elastic-Job 快速上手指南Elastic-Job支持 JAVA API 和 Spring 配置两种方式配置任务&#xff0c;这里我们使用 JAVA API 的形式来创建一个简单的任务入门&#xff0c;现在都是 Spring Boot 时代了&#xff0c;所以不建议使用 Spring 配置文件的形式。 Elast…

7-指令系统

README】 1.本文总结自bilibili《计算机组成原理&#xff08;哈工大刘宏伟&#xff09;》的视频讲解&#xff0c;非常棒&#xff0c;墙裂推荐&#xff1b; 机器指令&#xff1a; CPU能够识别并且执行的操作命令&#xff1b;有可以理解为不同cpu&#xff0c;在生产时预设了一组…

WeText项目:一个基于.NET实现的DDD、CQRS与微服务架构的演示案例

最近出于工作需要&#xff0c;了解了一下微服务架构&#xff08;Microservice Architecture&#xff0c;MSA&#xff09;。我经过两周业余时间的努力&#xff0c;凭着自己对微服务架构的理解&#xff0c;从无到有&#xff0c;基于.NET打造了一个演示微服务架构的应用程序案例&a…

常用的数据交换格式有哪些_高程数据格式介绍

高程数据格式介绍1 概述目前业内常用的地形数据格式有img,tif,grp及dem,其中dem在地形表达中能力最强&#xff0c;效果最好&#xff0c;本文围绕dem的数据格式进行介绍。2 DEM简介数字高程模型(Digital Elevation Model)&#xff0c;简称DEM&#xff0c;是通过有限的地形高程数…

switch case 支持的 6 种数据类型

转载自 switch case 支持的 6 种数据类型那么今天就讲一下 Java 中的 switch case 语句吧&#xff0c;有忘记的同学正好可以温习一下。 Java 中 switch case 语句用来判断一个变量与一系列值中某个值是否相等&#xff0c;每个值称为一个分支。 语法格式如下&#xff1a; switc…

1-操作系统启动前的工作

【README】 1.本文总结自B站《操作系统&#xff08;哈工大李治军老师&#xff09;》的视频讲解&#xff0c;非常棒&#xff0c;墙裂推荐&#xff1b; 【1】计算机上电 1&#xff09; 问题&#xff1a; 这神秘的黑色背后发生了什么 &#xff1f;计算机是怎么工作的&#x…

傅里叶变换公式_理解1维傅里叶变换

上个学期&#xff0c;学习了信号与系统。虽然知道了傅里叶变换的作用以及如何使用。但是对于它的本质&#xff0c;也就是FT是如何探测到频率的尚有疑惑。而恰好在知乎上发现了一些很好的回答。故将这些回答整理在这。感性理解1维FT知友Heinrich写的傅里叶分析之掐死教程&#x…

ASP.NET Core的配置(3): 将配置绑定为对象

出于编程上的便利&#xff0c;我们通常不会直接利用ConfigurationBuilder创建的Configuration对象读取某个单一配置项的值&#xff0c;而是倾向于将一组相关的配置绑定为一个对象&#xff0c;我们将后者称为Options对象。我们在《ASP.NET Core的配置&#xff08;1&#xff09;&…

11 个简练的 Java 性能调优技巧

转载自 11 个简练的 Java 性能调优技巧想要让你的项目一直高性能运作吗&#xff1f;以下有一些技巧你可以拿去消除缓存瓶颈&#xff0c;还有一些其他的性能调优建议。 大多数开发者认为性能优化是一个复杂的话题&#xff0c;它需要大量的工作经验和相关知识理论。好吧&#xff…

pre1-flink理论-批处理与流处理+简单示例

【README】 1.本文包含了 批处理与流处理的代码示例&#xff1b; 批处理&#xff1a;把数据 攒在一起&#xff08;或攒一段时间或攒一定内存大小&#xff09;&#xff0c;然后再处理&#xff0c;这叫批处理&#xff1b;流处理&#xff1a;数据每来一个就处理一个&#xff1b;…