hashmap为什么是2的倍数_HashMap源码解析(jdk1.8)

219340d24976f680c47d8ddf3981e31a.png

HashMap在java中使用的频率很高,同时也是面试时的必问的问题。今天咱们就来学习下jHashMap的源码,版本为jdk1.8。学习之前,先一起了解下HashMap的数据结构,便于理解后面所讲的内容。

HashMap的底层数据结构

fe23128e86600b6d076fb96721138fd2.png

由图可见,HashMap主要是由 数组+链表+红黑树 构成的。最外层是一个数组,数组中的每一个元素称作桶(segment),每个桶中存在着链表或红黑树,其中链表或红黑树中的每一个元素又称作bin。

简单的描述下put的步骤。往map中put键值对时,首先计算键值对中key的hash值,以此确定插入数组中的位置(也就是下标值),但是可能存在同一hash值的元素已经被放在数组同一位置了,这种现象称为碰撞,这时按照尾插法(jdk1.7及以前为头插法)的方式添加key-value到同一hash值的元素的后面,链表就这样形成了。当链表长度超过8时,链表自动转换为红黑树。

静态全区变量

/*** 默认初始化容量,值为16* 必须是2的n次幂.*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16/*** 最大容量, 容量不能超出这个值。如果一个更大的初始化容量在构造函数中被指定,将被MAXIMUM_CAPACITY替换.* 必须是2的倍数。最大容量为1<<30,即2的30次方。*/
static final int MAXIMUM_CAPACITY = 1 << 30;/*** 默认的加载因子。*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;/*** 将链表转化为红黑树的临界值。* 当添加一个元素被添加到有至少TREEIFY_THRESHOLD个节点的桶中,桶中链表将被转化为树形结构。* 临界值最小为8*/
static final int TREEIFY_THRESHOLD = 8;/*** 恢复成链式结构的桶大小临界值* 小于TREEIFY_THRESHOLD,临界值最大为6*/
static final int UNTREEIFY_THRESHOLD = 6;/*** 桶可能被转化为树形结构的最小容量。当哈希表的大小超过这个阈值,才会把链式结构转化成树型结构,否则仅采取扩容来尝试减少冲突。* 应该至少4*TREEIFY_THRESHOLD来避免扩容和树形结构化之间的冲突。*/
static final int MIN_TREEIFY_CAPACITY = 64;

一起走遍HashMap的流程(举个栗子)

  1. 初始化HashMap
public static void main(String[] args) {HashMap<String, String> hashMap = new HashMap<>(2);hashMap.put("java", "爪哇");String java= hashMap.get("java");System.out.println(java);}

由于我们预计会放入一个元素,出于性能考虑,我们将容量设置为 2,既保证了性能,也节约了空间

   /*** 初始化时进入的第一个方法*/public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}/*** 初始化时进入的第二个方法,传入参数有(容量值,加载因子)* 流程解析:如果初始容量小于零,则抛出异常;如果初始容量大于最大容量,将最大容量值赋值给初始容量;如果加载因子小于零也会抛出异常* 接着对负载因子进行赋值,最后通过特定方法计算阀值(无论放入任何一个int 数字,都能找到离他最近的 2 的幂次方数字(并且比他大)并赋值*/public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);this.loadFactor = loadFactor;this.threshold = tableSizeFor(initialCapacity);}

上面是 HashMap 的两个构造方法,其中,我们设置了初始容量为 2, 而默认的加载因子我们之前说过:0.75,当然也可以自己设置,但 0.75 是最均衡的设置,没有特殊要求不要修改该值,加载因子过小,理论上能减少 hash 冲突,加载因子过大可以节约空间,减少 HashMap 中最耗性能的操作:reHash。

2.往HashMap中put键值对

   /*** put时进入的第一个方法*/
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}/*** put时进入的第二个方法(计算key的hash值)* 流程解析:当key为null时,就返回零;不为null,则进入下一步计算,首先算出key的hashcode,当前key为“java”,则h=3254818,然后h* 异或h无符号右移16位的值,返回值为3254803*/
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}/*** put时进入的第三个方法*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;// 当前对象的数组是null 或者数组长度时0时,则需要初始化数组if ((tab = table) == null || (n = tab.length) == 0)// 得到数组的长度 16n = (tab = resize()).length;// 如果通过hash值计算出的下标的地方没有元素,则根据给定的key 和 value 创建一个元素if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else { // 如果hash冲突了Node<K,V> e; K k;// 如果给定的hash和冲突下标中的 hash 值相等并且 (已有的key和给定的key相等(地址相同,或者equals相同)),说明该key和已有的key相同if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))// 那么就将已存在的值赋给上面定义的e变量e = p;// 如果以存在的值是个树类型的,则将给定的键值对和该值关联。else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);// 如果key不相同,只是hash冲突,并且不是树,则是链表else { // 循环,直到链表中的某个节点为null,或者某个节点hash值和给定的hash值一致且key也相同,则停止循环。for (int binCount = 0; ; ++binCount) {// 如果next属性是空if ((e = p.next) == null) {// 那么创建新的节点赋值给已有的next 属性p.next = newNode(hash, key, value, null);// 如果树的阀值大于等于7,也就是,链表长度达到了8(从0开始)。if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st// 如果链表长度达到了8,且数组长度小于64,那么就重新散列,如果大于64,则创建红黑树treeifyBin(tab, hash);// 结束循环break;}// 如果hash值和next的hash值相同且(key也相同)if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))// 结束循环break;// 如果给定的hash值不同或者key不同。// 将next 值赋给 p,为下次循环做铺垫p = e;}}// 通过上面的逻辑,如果e不是null,表示:该元素存在了(也就是他们呢key相等)if (e != null) { // existing mapping for key// 取出该元素的值V oldValue = e.value;// 如果 onlyIfAbsent 是 true,就不要改变已有的值,这里我们是false。// 如果是false,或者 value 是nullif (!onlyIfAbsent || oldValue == null)// 将新的值替换老的值e.value = value;// 访问后回调afterNodeAccess(e);// 返回之前的旧值return oldValue;}}// 如果e== null,需要增加 modeCount 变量,为迭代器服务。++modCount;// 如果数组长度大于了阀值if (++size > threshold)// 重新散列resize();// 插入后回调afterNodeInsertion(evict);// 返回nullreturn null;}

该方法为 HashMap 的核心方法,以下是该方法的步骤。

①判断数组是否为空,如果是空,则创建默认长度位 16 的数组。

②通过与运算计算对应 hash 值的下标,如果对应下标的位置没有元素,则直接创建一个。

③如果有元素,说明 hash 冲突了,则再次进行 3 种判断。

1.判断两个冲突的key是否相等,equals 方法的价值在这里体现了。如果相等,则将已经存在的值赋给变量e。最后更新e的

value,也就是替换操作。

2.如果key不相等,则判断是否是红黑树类型,如果是红黑树,则交给红黑树追加此元素。

3.如果key既不相等,也不是红黑树,则是链表,那么就遍历链表中的每一个key和给定的key是否相等。如果,链表的长度

大于等于8了,则将链表改为红黑树,这是Java8 的一个新的优化。

④最后,如果这三个判断返回的 e 不为null,则说明key重复,则更新key对应的value的值。

⑤对维护着迭代器的modCount 变量加一。

⑥最后判断,如果当前数组的长度已经大于阈值了。则重新hash。

95241c9021661b2646fb1034cbb23b61.png
链表列下第二个菱形的条件中,加一个转为为红黑树时还要判断table.length 是否小于 MIN_TREEIFY_CAPACITY=64的条件

3.根据键get值

/***  get时进入的第一个方法* 返回指定的key映射的value,如果value为null,则返回null。*/
public V get(Object key) {Node<K,V> e;//如果通过key获取到的node为null,则返回null,否则返回node的value。getNode方法的实现就在下面。return (e = getNode(hash(key), key)) == null ? null : e.value;
}

get(E e)可以分为三个步骤:

  1. 通过hash(Object key)方法计算key的哈希值hash。
  2. 通过getNode( int hash, Object key)方法获取node。
  3. 如果node为null,返回null,否则返回node.value。
/***g et时进入的第二个方法* 根据key的哈希值和key获取对应的节点* * @param hash 指定参数key的哈希值* @param key 指定参数key* @return 返回node,如果没有则返回null*/
final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;//如果哈希表不为空,而且key对应的桶上不为空if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {//如果桶中的第一个节点就和指定参数hash和key匹配上了if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))//返回桶中的第一个节点return first;//如果桶中的第一个节点没有匹配上,而且有后续节点if ((e = first.next) != null) {//如果当前的桶采用红黑树,则调用红黑树的get方法去获取节点if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);//如果当前的桶不采用红黑树,即桶中节点结构为链式结构do {//遍历链表,直到key匹配if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}//如果哈希表为空,或者没有找到节点,返回nullreturn null;
}

getNode方法又可分为以下几个步骤:

①如果哈希表为空,或key对应的桶为空,返回null

②如果桶中的第一个节点就和指定参数hash和key匹配上了,返回这个节点。

③如果桶中的第一个节点没有匹配上,而且有后续节点

1.如果当前的桶采用红黑树,则调用红黑树的get方法去获取节点

2.如果当前的桶不采用红黑树,即桶中节点结构为链式结构,遍历链表,直到key匹配

④找到节点返回null,否则返回null。

3.resize() 扩容机制

声明一个hashmap时不给它一个容量值时,hashmap会默认的容量值为16。若声明时给定的容量值非2的n次幂,则会自动转为2的n次幂,比如初始值给的5,hashmap会自动转换为8。

如果 put值的数量大于阈值时,hashmap就会执行扩容,其中阈值为数组长度*加载因子。比如我们使用hashmap的默认容量16时,这时阈值=0.75*16=12,接着我们再put第十三个数据时,hashmap就开始扩容,扩容之后的长度为原长度的2倍,也是32。扩容就是把原来的小水桶废弃,直接用更大的水桶替换。

29fb3d71e8b8beea397241132c61bf63.png

PS:部分图文来源网络(侵删)

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

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

相关文章

消息队列_消息队列:kafka

概念kafka是一个分布式的基于发布/订阅模式的消息队列&#xff0c;主要用于大数据实时处理领域。要理解kafka首先要有分布式的概念&#xff0c;要有消息队列的概念。分布式系统最大的优势就是解耦和削峰&#xff0c;这种情况下&#xff0c;A系统生成了一个消息&#xff0c;B系统…

kopernio显示无效程序_陆风路虎外观设计专利无效案一锤定音,最高法:陆风X7专利无效...

点击上方“华商报”可快速关注哦&#xff01;持续5年多的路虎、陆风外观设计专利有效性之争终于尘埃落定&#xff1a;华商报记者日前从代理律师处获悉&#xff0c;最高人民法院近日驳回了江铃控股有限公司的再审请求&#xff0c;这意味着&#xff0c;陆风X7的外观专利无效。陆风…

掩膜区域内像素值_MRI ADC值是怎么来的?咱们来手算一下

首发公众号“医影杂记”ADC&#xff08;Apparent diffusion coefficient&#xff09;&#xff0c;表观弥散系数&#xff0c;用于描述DWI序列中不同方向的分子扩散运动的速度和范围&#xff0c;是MRI DWI&#xff08;Diffusion-weighted imaging, 弥散加权成像&#xff09;中最常…

计算机上的查找替换功能快速格式化,Word2013文档中使用查找和替换功能来快速更改文本格式的方法...

在对文档进行处理时灵活使用Word的查找和替换功能将能够取得事半功倍的效果。下面介绍Word2013文档中使用查找和替换功能来快速更改文本格式的方法。1、在“开始”选项卡中单击“编辑”组中的“替换”按钮&#xff0c;打开“查找和替换”对话框&#xff0c;切换到“替换”选项卡…

composer 依赖包版本冲突_composer快速入门教程

php中文网最新课程每日17点准时技术干货分享Composer 是 PHP 的一个依赖管理工具。我们可以在项目中声明所依赖的外部工具库&#xff0c;Composer 会帮你安装这些依赖的库文件&#xff0c;有了它&#xff0c;我们就可以很轻松的使用一个命令将其他人的优秀代码引用到我们的项目…

极品飞车ol服务器维护,《极品飞车OL》配件升级常见问题介绍

今天九游和大家讲解《极品飞车OL》配件升级常见问题介绍极品飞车OL游戏中&#xff0c;玩家们是需要不断使用配件加强自己的车辆来提升能力值。那么关于配件升级玩家们整体的有什么问题呢&#xff1f;下面小编也为大家带来了相关的介绍。感兴趣的玩家快来看看吧。《极品飞车OL》…

linq判断集合中相同元素个数_JavaSe集合的概念以及集合框架介绍

###集合今天任务1.概念1.1 集合的概念1.2 集合的框架结果介绍1.3 集合和数组的对比 2.Collection接口2.1 Collections中常用的方法 3.泛型3.1 什么是泛型3.2 泛型的声明3.3 说明3.4 泛型使用时的注意事项3.5 受限泛型3.6 泛型应用在集合上 4.Iterator迭代器4.1 迭代器的工作原理…

华为手机获取状态栏高度是错误的_你的华为手机状态栏有HD图标吗?这又代表着什么?看完你就懂了...

平时比较细心的朋友应该都发现了&#xff0c;华为手机的状态栏(信号栏)有个"HD"图标。有些朋友看到这个图标&#xff0c;可能会担心自己手机是不是出问题了&#xff1f;会不会对手机有什么影响&#xff1f;能不能把它关闭&#xff1f;下面就给大家解惑。1. "HD&…

ext.ajax.request跨域,跨域Ajax访问header中 x-requested-with丢失

前端调用后端接口&#xff0c;本域情况下&#xff0c;ajax方式调用&#xff0c;request header中包含x-requested-with信息。跨域情况下&#xff0c;request header中不再包含x-requested-with。说明&#xff1a;1.前端ajax封装的jquery的$.ajax方法。2.后端header相关设置已允…

bread是可数还是不可数_可数名词不可数名词分不清?出题老师告诉你方法

我是瓶子老师。我有10多年初中英语教学经验&#xff0c;参与过中考一模、二模命题&#xff0c;同时我也是《学生双语报》的撰稿人&#xff0c;为本市超过5万名中考生出过中考模拟试题。在初中英语的学习过程中&#xff0c;我们会讲到可数名词和不可数名词。很多同学总是记不住哪…

nginx指定配置文件启动_【第1717期】Nginx入门指南

前言Nginx有在部署早读课网站的时候用到&#xff0c;平时用的不多&#xff0c;又是一个可以顺便了解的知识点。那个李晓云&#xff0c;这篇是你想了解的吗&#xff1f;今日早读文章由慕小白翻译分享。正文从这开始&#xff5e;&#xff5e;这份指南是对nginx的基本介绍&#xf…

bash: pcre-config: 未找到命令..._Docker 常用操作命令

文章首发于微信公众号《程序员果果》 地址&#xff1a;https://mp.weixin.qq.com/s/S9VkzSJx_JOY8zDiC_nuEg1. 启动容器docker run IMAGE [COMMAND] [ARG...]IMAGE 是指启动容器所使用的操作系统镜像[COMMAND] [ARG...] 指的是容器启动后运行的命令及其参数2. 启动交互式容器#…

es 全量同步mysql_使用canal将mysql同步到es中

因为自己项目中需要用到mysql数据同步到es中&#xff0c;查找了相关资料最后决定用canal来做&#xff0c;所以便有了本文&#xff0c;下面一起来看如何使用canal吧canal教程根据 https://github.com/alibaba/canal 上的原理解释&#xff0c;我们知道 canal 会模拟 mysql slave …

一个列中多行求和_excel表格制作,Excel表格的基本操作,包含制作一个表格10方面的知识(1)...

蒹葭苍苍&#xff0c;白露为霜。所谓伊人&#xff0c;在水一方。溯洄从之&#xff0c;道阻且长。溯游从之&#xff0c;宛在水中央。蒹葭萋萋&#xff0c;白露未晞。所谓伊人&#xff0c;在水之湄。溯洄从之&#xff0c;道阻且跻。溯游从之&#xff0c;宛在水中坻。蒹葭采采&…

实时监控后台数据 vue_实时数据监控,快速掌握B站爆款视频热度走向

飞瓜数据B站版的数据监控功能提供对B站UP主监控功能&#xff0c;实时掌握UP主视频热度走向和投放效果。通过监控输入B站视频链接&#xff0c;设定24/48时长开始对其监控。监控开始后在监控报告中查看对应视频的数据曲线变化或者等待监控结束直接观察整个的曲线走势。实现分钟打…

帧同步_帧同步和状态同步该怎么选(上)

这是一篇拖延了2年多的文章…2017年10月份开始写的&#xff0c;直到这次过年才写完。。。前言随着王者荣耀的崛起&#xff0c;使用帧同步&#xff08;Lockstep&#xff09;的游戏也越来越多&#xff0c;关于帧同步和状态同步的讨论争论也有不少&#xff0c;那么到底该选哪种同步…

实现连麦_直播课程系统如何实现互动连麦效果?

比起录播课&#xff0c;直播课师生能够更好地互动&#xff0c;因此很多老师利用直播课程系统进行直播教学。直播课程系统的连麦互动往往能让直播课充满生机。在直播教学中使用连麦互动功能&#xff0c;老师可以随时提问学生&#xff0c;学生也可以就不懂的问题询问老师&#xf…

c++ 二次开发 良田高拍仪_在网页中调用摄像头实现拍照上传 - 高拍仪二次开发...

来源于 https://blog.csdn.net/weixin_40659738/article/details/78252562在网页中调用摄像头实现拍照上传高拍仪二次开发在一些公共部门的办事处&#xff0c;比如银行、护照办理中心、税务等&#xff0c;我们可能会注意到办公桌上摆着这样一台机器。办公人员用它拍摄各种证件…

复频域求零输入响应_第十四章 动态电路的复频域分析 习题答案

第十四章 动态电路的复频域分析一、选择题1&#xff0e; 图13—1所示电感元件的电压、电流关系的运算形式是 B 。A &#xff0e;)0()()(-L L L Li s sLI s U &#xff1b;B &#xff0e;)0()()(--L L L Li s sLI s U &#xff1b;C &#xff0e;si s sLI s U L L L )0()()(- 2&…

cancase vector_基于Vector总线设备的CAN总线测试方法概述

3.3采样点位置测试测试设备&#xff1a;CANoe、CANStressDR。测试系统架构中需将CANScope旁路处理。测试设备正确接线后(CANStressDR串接在回路中)&#xff0c;CANoe发送报文&#xff0c;CANStressDR采用位干扰的方式进行干扰&#xff0c;具体是采用CANStressDR从后往前逐位干扰…