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,一经查实,立即删除!

相关文章

c语言 单词变复数_一些复数运算的C语言实现

1 /*file ComplexCalculation.c2 *author Vincent Cui3 *e-mail whcui1987163.com4 *version 0.15 *data 20-Oct-20146 *brief 用于复数运算的一些函数7 */8910 #include "ComplexCalculation.h"11 #include "math.h"12 #include "stdio.h"13141…

计算机考试前的心情作文,考试时的心情作文(七篇)

星期五&#xff0c;我们学校举行了期中考试。“叮铃铃&#xff0c;叮铃铃。”上课了&#xff0c;同学们立刻坐整齐&#xff0c;等待着老师的到来&#xff0c;教室里顿时变得鸦雀无声。一会儿&#xff0c;老师拿着试卷来了。我的心里立刻像有着小兔子一样&#xff0c;非常紧张。…

ts 模板库文件_在ts文件中使用模板引用变量的方法

ViewChildimport { Component, ViewChild, AfterViewInit } from angular/core;//假设的。这里引入你用到的table组件类import { TableComponent } from PrimeNGComponent({selector: app-xxx,templateUrl: ./xxx.component.html,styleUrls: [./xxx.component.css]})export cla…

计算机二级web程序设计习题,2015年计算机二级《Web程序设计》习题及答案(5)

一、单选题1、在超级链接中&#xff0c;如果指定___________框架名称&#xff0c;连接目标将在链接文本所在的框架页内出现&#xff0c;当前页面被刷新。(D)A、BlankB、SelfC、ParentD、Top2下面关于标记的说法不正确的是___________。(C)A、标记要填写在一对尖括号(<>)内…

消息队列_消息队列:kafka

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

简单 局部 整体光照模型计算机图形学,计算机图形学北大光照模型.ppt

计算机图形学北大光照模型* 当来自光源和周围环境的入射光照射在景物表面上时&#xff0c;将有部分光被反射或透射&#xff1b; 在视线方向的反射或透射光&#xff0c;被观察者眼睛中的视觉细胞接收 人眼中的杆状及锥状细胞接收光线&#xff0c;产生视觉 * * Insulator:绝热体 …

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;中最常…

我的家计算机教学反思,《我的家》教学反思

文章来源初 中教师cz 91 0.cOm《我的家》是一篇表达对爸爸妈妈爱的短诗&#xff0c;在我上完课后并在黄老师的指导下得出了以下几点反思。首先是好的地方&#xff1a;一&#xff1a;识字教学对于一年级语文来说识字应该是很重要的环节&#xff0c;所以我在识字教学过程中充分渗…

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

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

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

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

计算机少年宫辅导教师总结,微机兴趣小组活动总结

少年宫活动微机小组总结为了迎接新世纪的挑战&#xff0c;为了全面推行教育&#xff0c;培养学生全面发展&#xff0c;我们学校继上学期继续开展了丰富多彩的第二课堂兴趣小组&#xff0c;为那些有各种特长的学生了发展的机会&#xff0c;对他们进行了专业知识方面的系统训练&a…

python一个月能掌握吗_Python 从入门到精通:一个月足够了!

毫无疑问&#xff0c;Python 是当下最火的编程语言之一。对于许多未曾涉足计算机编程的领域「小白」来说&#xff0c;深入地掌握 Python 看似是一件十分困难的事。其实&#xff0c;只要掌握了科学的学习方法并制定了合理的学习计划&#xff0c;Python 从 入门到精通只需要一个月…

联想服务器开机引导,联想服务器怎么进入bios

联想电脑设置起来比较麻烦&#xff0c;除了快捷启动菜单比较方便&#xff0c;如果要用传统的方式进行设置&#xff0c;会有很多项要设置。那么你知道联想服务器怎么进入bios吗?接下来&#xff0c;学习啦小编跟你分享联想服务器进入bios的设置步骤图解。联想服务器进入bios的设…

boot lib分离 spring_SpringBoot打包分离依赖jar和资源文件

网上找了很多 maven-dependency-plugin 在我的电脑试了很多次都无法把依赖的jar独立下载到target/lib目录 暂时使用下面折中的方法 (缺点是每次修改依赖可能都需要重新执行默认sb的打包 以便手工拷贝jar)默认springboot打包配置为&#xff1a;samplesrc/main/java**/*.xmlsrc/m…

极品飞车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 迭代器的工作原理…

vue页面取ajax返回值,Vue前端交互模式、Promise用法(回调地狱)

Promise 概述Promise 是异步编程的一种解决方案&#xff0c;从语法上讲&#xff0c;Promise 是一个对象&#xff0c;从它可以获取异步操作的消息。优点&#xff1a;可以避免多层异步调用嵌套问题(回调地狱)Promise 对象提供了简洁的 API&#xff0c;使得控制异步操作更加容易Pr…

华为手机获取状态栏高度是错误的_你的华为手机状态栏有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相关设置已允…