HashMap内部结构深入剖析
2019独角兽企业重金招聘Python工程师标准>>>
java.util.HashMap是很常见的类,前段时间公司系统由于对HashMap使用不当,导致cpu百分之百,在并发环境下使用HashMap而没有做同步,可能会引起死循环,关于这一点,sun的官方网站上已有阐述,这并非是bug。
HashMap的数据结构
HashMap主要是用数组来存储数据的,我们都知道它会对key进行哈希运算,哈系运算会有重复的哈希值,对于哈希值的冲突,HashMap采用链表来解决的。在HashMap里有这样的一句属性声明:
transient Entry[] table;
Entry就是HashMap存储的数据,它拥有的属性如下
|
|
看到next了吗?next就是为了哈希冲突而存在的。比如通过哈希运算,一个新元素应该在数组的第10个位置,但是第10个位置已经有Entry,那么好吧,将新加的元素也放到第10个位置,将第10个位置的原有Entry赋值给当前新加的Entry的next属性。数组存储的链表,链表是为了解决哈希冲突的,这一点要注意。
几个关键的属性
存储数据的数组
transient Entry[] table; 这个上面已经讲到了
默认容量
|
|
最大容量
|
|
默认加载因子,加载因子是一个比例,当HashMap的数据大小>=容量*加载因子时,HashMap会将容量扩容
|
|
当实际数据大小超过threshold时,HashMap会将容量扩容,threshold=容量*加载因子
|
|
加载因子
|
|
HashMap的初始过程
构造函数1
|
|
重点注意这里
|
|
capacity才是初始容量,而不是initialCapacity,这个要特别注意,如果执行new HashMap(9,0.75);那么HashMap的初始容量是16,而不是9,想想为什么吧。
构造函数2
|
|
构造函数3,全部都是默认值
|
|
构造函数4
|
|
如何哈希
HashMap并不是直接将对象的hashcode作为哈希值的,而是要把key的hashcode作一些运算以得到最终的哈希值,并且得到的哈希值也不是在数组中的位置哦,无论是get还是put还是别的方法,计算哈希值都是这一句:
|
|
hash函数如下:
|
|
useNewHash声明如下:
|
|
这说明useNewHash其实一直为false且不可改变的,hash函数里对useNewHash的判断真是多余的。
|
|
其实HashMap的哈希函数会一直都是oldHash。
如果确定数据的位置
看下面两行
|
|
第一行,上面讲过了,是得到哈希值,第二行,则是根据哈希指计算元素在数组中的位置了,位置的计算是将哈希值和数组长度按位与运算。
|
|
put方法到底作了什么?
|
|
如果key为NULL,则是单独处理的,看看putForNullKey方法:
|
|
NULL_KEY的声明:
|
|
这一段代码是处理哈希冲突的,就是说,在数组某个位置的对象可能并不是唯一的,它是一个链表结构,根据哈希值找到链表后,还要对链表遍历,找出key相等的对象,替换它,并且返回旧的值。
|
|
如果遍历完了该位置的链表都没有找到有key相等的,那么将当前对象增加到链表里面去
|
|
且看看addEntry方法
|
|
table[bucketIndex] = new Entry(hash, key, value, e);新建一个Entry对象,并放在当前位置的Entry链表的头部,看看下面的 Entry构造函数就知道了,注意红色部分。
|
|
如何扩容?
当put一个元素时,如果达到了容量限制,HashMap就会扩容,新的容量永远是原来的2倍。
上面的put方法里有这样的一段:
|
|
这是扩容判断,要注意,并不是数据尺寸达到HashMap的最大容量时才扩容,而是达到 threshold指定的值时就开始扩容, threshold=最大容量*加载因子。 看看resize方法
|
|
重点看看红色部分的 transfer方法
|
|
tranfer方法将所有的元素重新哈希,因为新的容量变大,所以每个元素的哈希值和位置都是不一样的。
正确的使用HashMap
1:不要在并发场景中使用HashMap
HashMap是线程不安全的,如果被多个线程共享的操作,将会引发不可预知的问题,据sun的说法,在扩容时,会引起链表的闭环,在get元素时,就会无限循环,后果是cpu100%。
看看get方法的红色部分
|
|
2:如果数据大小是固定的,那么最好给HashMap设定一个合理的容量值
根据上面的分析,HashMap的初始默认容量是16,默认加载因子是0.75,也就是说,如果采用HashMap的默认构造函数,当增加数据时,数据实际容量超过10*0.75=12时,HashMap就扩容,扩容带来一系列的运算,新建一个是原来容量2倍的数组,对原有元素全部重新哈希,如果你的数据有几千几万个,而用默认的HashMap构造函数,那结果是非常悲剧的,因为HashMap不断扩容,不断哈希,在使用HashMap的场景里,不会是多个线程共享一个HashMap,除非对HashMap包装并同步,由此产生的内存开销和cpu开销在某些情况下可能是致命的。
转载于:https://my.oschina.net/u/2274853/blog/394080
HashMap内部结构深入剖析相关推荐
- Java HashSet和HashMap源码剖析
转载自 Java HashSet和HashMap源码剖析 总体介绍 之所以把HashSet和HashMap放在一起讲解,是因为二者在Java里有着相同的实现,前者仅仅是对后者做了一层包装,也就是说Ha ...
- 【Java集合源码剖析】HashMap源码剖析
转载请注明出处:http://blog.csdn.net/ns_code/article/details/36034955 您好,我正在参加CSDN博文大赛,如果您喜欢我的文章,希望您能帮我投一票,谢 ...
- Java集合:HashMap源码剖析
一.HashMap概述 二.HashMap的数据结构 三.HashMap源码分析 1.关键属性 2.构造方法 3.存储数据 4.调整大小 5.数据读取 ...
- 详解集合之HashMap——HashMap内部结构,自动扩容机制,为什么需要重写hashcode和equals方法
HashMap底层实现是一个键值对Node数组,而Node实现了键值对Map.Entry接口 HashMap类继承结构图 1. HashMap对象的创建 1.1 默认的构造方法--只指定自动扩容时的加 ...
- HashSet与HashMap源代码深度剖析
HashSet源码分析: 先来看一下它的构造方法: 呃~~居然它的底层是用HashMap来实现的,颠覆三观,那它究竟是如何来用的呢?继续来往下跟: 对于HashSet而言是没有key->valu ...
- HashMap源代码深入剖析
1. 概述 首先从一个例子来开始HashMap的学习 public class Test {public static void main(String[] args) {Map<String, ...
- Java HashMap源码剖析
一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap ...
- 欧尼酱讲JVM(08)——字节码中方法内部结构的剖析
用到的工具--Jclasslib IDEA提供了插件,很方便. 首先 先看一段代码: public class LocalVariablesTest {private int count = 0;pu ...
- HashMap原理底层剖析
注意以下文章可能有描述和理解上的错误,如果出现错误请到评论区指出,我会第一时间修改问题.也希望文章能解决你的疑惑. HashMap结构图 HashMap底层数据结构:Entry数组+链表+红黑树(JD ...
最新文章
- html,css,js,反弹的js效果
- AI领域最最最稀缺的人才——AI架构师
- HttpModule
- 连接编码器_编码器与PLC的接线
- 安卓4.4玩java_Android4.4运行过程中闪退java.lang.NoClassDefFoundError
- python3.7界面_Python3.7+tkinter实现查询界面功能
- 安卓导航无信号无服务器,无信号导航能用否
- 安卓手机的a/span的宽高失效
- 【数字逻辑设计】组合电路
- 【XLL 框架库函数】 Excel/Excel12f
- RAC-DG 安装总结
- 计算机硬件无法启动不能读取文件,修复Windows出现的“文件或目录已损坏且无法读取”问题...
- 19-20年月度行业分析
- pr2020lut导入_lut调色预设怎么用?LUT预设导入fcpx/PR/AE/PS 详细教程
- 使用最新的跨平台框架Electron 实现 STM32 MCU 嵌入式系统的序列号烧写器上位机开发
- 语音转写(讯飞开放平台)工具类
- MaxCompute全套攻略
- 从 virtio 网卡收包段错误问题出发反思个人问题分析的过程
- IP签名档美化版api源码PHP
- JDF代码学习 JDF入门教程 代码配置