架构月亮姨 发表于 2020-3-3 20:49:56

史上超级详细:HashMap源码分析,你了解到源码的魅力了嘛

正文开始 注:JDK版本为1.8 本文分析直到增加方法,别的的删除修改等下文分析假如喜好请关注 关注公众号复兴 JDK领取 JDK阅读源码资料
HashMap1.8和1.8之前的源码差异很大
目次

简介
数据布局
类布局
属性
构造方法
增加
1.HashMap简介

HashMap基于哈希表的Map接口实现,是以key-value存储情势存在。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致雷同。)
HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。别的,HashMap中的映射不是有序的。在 JDK1.8 中,HashMap 是由 数组+链表+红黑树构成,新增了红黑树作为底层数据布局,布局变得复杂了,但是效率也变的更高效。
1.2 HashMap数据布局

在 JDK1.8 中,HashMap 是由 数组+链表+红黑树构成,新增了红黑树作为底层数据布局,布局变得复杂了,但是效率也变的更高效。当一个值中要存储到Map的时候会根据Key的值来计算出他的
hash,通过哈希来确认到数组的位置,假如发生哈希碰撞就以链表的情势存储 在Object源码分析中解释过,但是这样假如链表过长来的话,HashMap会把这个链表转换成红黑树来存储。
来看一下HashMap的存储布局
https://p9.pstatp.com/large/pgc-image/d8dd85b553914253836db573ede334f1
但是这样的话问题来了,HashMap为什么要使用红黑树呢,这样布局的话不是更麻烦了吗??
这个问题我也没有想过,其实很多在看的时候只会在乎红黑树的实现而忽略到了为什么要使用的这个问题,我也是在写本文的时候突发迷惑。参考了网上的例子,同时也解释了为什么阀值为8:
由于Map中桶的元素初始化是链表保存的,其查找性能是O(n),而树布局能将查找性能提升到O(log(n))。当链表长度很小的时候,即使遍历,速度也非常快,但是当链表长度不断变长,肯定会对查询性能有一定的影响,所以才需要转成树。至于为什么阈值是8,我想,去源码中找寻答案应该是最可靠的途径。
参考地址:https://dwz.cn/nPFXmXwJ
2.类布局

我们来看一下类布局
https://p1.pstatp.com/large/pgc-image/6e032b3e4073470894780a0c1a635aab
在阅读源码的时候不停有个问题很狐疑就是HashMap已经继承了AbstractMap而AbstractMap类实现了Map接口,那为什么HashMap还要在实现Map接口呢?同样在ArrayList中LinkedList中都是这种布局。
据 java 聚集框架的创始人Josh Bloch描述,这样的写法是一个失误。在java聚集框架中,雷同这样的写法很多,最开始写java聚集框架的时候,他认为这样写,在某些地方大概是有代价的,直到他意识到错了。显然的,JDK的维护者,厥后不认为这个小小的失误值得去修改,所以就这样存在下来了。
https://p9.pstatp.com/large/pgc-image/7c19d2e27d0c4c2bb139f02555a9c3c7

[*]Cloneable 空接口,表示可以克隆
[*]Serializable 序列化
[*]AbstractMap 提供Map实现接口
3.属性

初始化容量(必须是二的n次幂)
https://p1.pstatp.com/large/pgc-image/cf37bf3b402245fba91ed390bca2e2f2
聚集最大容量(必须是二的幂)
https://p3.pstatp.com/large/pgc-image/a7d08065cac54b1489e2a87814a8af37
负载因子,默认的0.75
https://p1.pstatp.com/large/pgc-image/882bba6e5288482ba0f63fe08df0cffb
当链表的值凌驾8则会转红黑树(1.8新增)
https://p9.pstatp.com/large/pgc-image/7e6abc9a988e41918be51e36f219fb84
当链表的值小于6则会从红黑树转回链表
https://p3.pstatp.com/large/pgc-image/b303cf39a5f34c7991f85aecccdeec13
当Map里面的数目凌驾这个值时,表中的桶才能进行树形化 ,否则桶内元素太多时会扩容,而不是树形化 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
https://p1.pstatp.com/large/pgc-image/d590002c7af94281aa5ab7cdc11149b4
table用来初始化(必须是二的n次幂)
https://p3.pstatp.com/large/pgc-image/039174cadaa74eefbb331e118be2d37b
用来存放缓存
https://p9.pstatp.com/large/pgc-image/093d10a1d3374ba5bb92439dd462c360
HashMap中存储的数目
https://p1.pstatp.com/large/pgc-image/bc60f351fa574ad48f55000c3ca32ed8
用来记载HashMap的修改次数
https://p3.pstatp.com/large/pgc-image/5b29e6a985d849c8b2a3b7dbd7d9f364
用来调解巨细下一个容量的值计算方式为(容量*负载因子)
https://p3.pstatp.com/large/pgc-image/aaa1ec42bfd84731acd4728bb3e81307
哈希表的加载因子
https://p1.pstatp.com/large/pgc-image/fcdadd2f4b154f33aeebb1930ecdc641
重点属性

[*]table在JDK1.8中我们了解到HashMap是由数组加链表加红黑树来组成的布局其中table就是HashMap中的数组
[*]size为HashMap中K-V的实时数目
[*]loadFactor加载因子,是用来衡量 HashMap 满的程度,计算HashMap的实时加载因子的方法为:size/capacity,而不是占用桶的数目去除以capacity。capacity 是桶的数目,也就是 table 的长度length。
[*]threshold计算公式:capacity * loadFactor。这个值是当前已占用数组长度的最大值。过这个数目就重新resize(扩容),扩容后的 HashMap 容量是之前容量的两倍
4.构造方法

开始看构造方法。
4.1 HashMap()

构造一个空的 HashMap ,默认初始容量(16)和默认负载因子(0.75)。
https://p1.pstatp.com/large/pgc-image/6eef8f5dd3834301bfa48f19d04d2cd8
4.2 HashMap(int initialCapacity)

构造一个空的 HashMap具有指定的初始容量和默认负载因子(0.75)。
https://p3.pstatp.com/large/pgc-image/d92827aabdef42baa43c9a433f0040d4
4.3 HashMap(int initialCapacity, float loadFactor)

构造一个空的 HashMap具有指定的初始容量和负载因子。我们来分析一下。
https://p1.pstatp.com/large/pgc-image/09af1a70e4f74743a2ba6b24d836c4f4
最后调用了tableSizeFor,来看一下方法实现:
https://p3.pstatp.com/large/pgc-image/c131f3ec04b1466c86fa12315ec744e4
5.增加

如今我们开始分析put()方法
https://p3.pstatp.com/large/pgc-image/05a75cc80e324df2b2c07390b35f3a4d
我们可以看到put调用的是putVal来进行数据插入,但是要注意到key在这里执行了一下hash()方法,来看一下Hash方法是如何实现的。
https://p3.pstatp.com/large/pgc-image/55c9cb6348bb4cada981833dd819310b
从上面可以得知HashMap是支持Key为空的,而HashTable是直接用过Key来获取HashCode所以key为空会抛异常其实上面就已经解释了为什么HashMap的长度为什么要是2的幂由于HashMap 使用的方法很奇妙,它通过 hash & (table.length -1)来得到该对象的保存位,前面说过 HashMap 底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当 length 总是2的n次方时,hash & (length-1)运算等价于对 length 取模,也就是 hash%length,但是&比%具有更高的效率。比如 n % 32 = n & (32 -1)。
如今看putVal()方法,看看它到底做了什么。
重要参数:

[*]hash key的hash值
[*]key 原始Key
[*]value 要存放的值
[*]onlyIfAbsent 假如true代表不更改现有的值
[*]evict 假如为false表示table为创建状态
完整源码分析,放图片的话会太长了,所以就截取了一下分为两部。
https://p1.pstatp.com/large/pgc-image/5cd8efe328e640468e08d3d530822153
https://p3.pstatp.com/large/pgc-image/c5964871a26a404a90584b25818eb6a3
最后:
假如看完这篇文章以为对你另有那么一点开导的话烦请关注+转发一波让更多的人看到哦,笔者也会定期更新技醒目货和大家一起学习交流哦!笔者也整理收集了一份笔记和讲解视频。需要的小伙伴可以加个人ID。私信复兴“笔记”获取ID。


https://p1.pstatp.com/large/pgc-image/3bf6b08b9eea41f5a48f1f57ec1cbd51


https://p3.pstatp.com/large/pgc-image/88a53f155c484c789043d0a18500fb5f


https://p3.pstatp.com/large/pgc-image/397fe38e8f2e4c1caf195ea431f3145d
需要的小伙伴可以加个人ID。私信复兴“笔记”获取ID。

抖音信使 发表于 2020-4-29 04:08:40

阈值,不是阀值[惊呆]

GuangZ 发表于 2020-3-4 00:38:25

转发了

YuDong4 发表于 2020-3-4 00:29:29

转发了

挂机孙悟空 发表于 2020-3-4 09:40:37

转发了

小虎Cola 发表于 2020-3-6 08:06:36

转发了

用户2894865971942 发表于 2020-3-9 00:43:00

转发了

天高云淡25078 发表于 2020-3-4 19:21:51

转发了

Marianas 发表于 2020-3-8 10:00:22

转发了

Vision1116 发表于 2020-3-4 03:08:22

转发了
页: [1] 2
查看完整版本: 史上超级详细:HashMap源码分析,你了解到源码的魅力了嘛