HashMap底层详讲
【JDK1.7】
底层数据结构:
存储:hashMap存储的是键值对,允许key为null,也允许value为null。
内部:位桶数组+ 链表
特点:同一hash值的链表都存储在一个链表里,当位于一个桶中的元素较多,即发生hash冲突比较多时,HashMap会将同一个桶中的数据以链表的形式存储,通过key值依次查找的效率较低。
【JDK1.8】
底层数据结构:
存储:hashMap存储的是键值对,允许key为null,也允许value为null。
内部:位桶数组 + 链表 + 红黑树
特点:同一hash值的链表都存储在一个链表里,当位于一个桶中的元素较多,HashMap会将同一个桶中的数据以链表的形式存储,如果链表长度到达阀值(默认是8),就会将链表转换为红黑树。
注意:
1、 创建一个Map时,并不会初始化位桶数组。
只会计算出根据初始容量(这里是12)计算出 threshold 为16。
由于tableSizeFor(initialCapacity)方法
不管你传的初始容量值是9、10、11、12、13…16,其计算出来n+1永远为16(2的n次幂)
所以,tableSizeFor(initialCapacity)方法的真实目的是为了确保所有map初始化时的容量均为2的n次幂。
2、 第一次put时,会调用到putVal方法,此时的threshold 早已经在我们new HashMap的时候计算出并赋值为为16。
1.检查table是否为空,table的长度是否为0
2.只要满足其中一条,就会首先对table进行一次resize()扩容(这也是集合的首次扩容)
如源码所示,初始容量会由threshold去初始化。所以可以看到,初始化给的12,而真正初始化的map容量大小其实是2的n次幂(这里是16)。
而真正的阈值是在699行这里进行的,可以看到其阈值最大值不能超过Integer.MAX_VALUE,
而容量的阈值不能大于MAXIMUM_CAPACITY(1 << 30)即2的30次幂:
好,现在开始计算插入的元素应该在位桶的哪个位置,如下,看代码632行:
根据i = (n -1) & hash 算出其位置,n为容量16,hash值为49,代入算出i = 1,也就是位桶下标为1的位置,并在该位置创建一个节点Node。
继续调试,执行完放入元素之后modCount自增,size自增,并和扩容阈值(当前是12)比较,1小于12不用扩容
3、 当put一个key相同的键值对
我们来分析其源码:
此时,key的hash值相同,定位的位置已经有元素了,所以走了源码635-637行
由于目前元素没有过多,所以暂时并未红黑树化,所以跳过其他else直接来到654行进行值的覆盖。
4、 为了触发扩容,将其一直put到”10”,发现”10”的hash值瞬间大到1567,那位置可能大概率就不连续了。
果然,以前通过i = (n -1) & hash算出来的下标位置都是连续的1~9,而key=”10” 通过i = (n -1) & hash计算出其下标位置变成15了!
而put(“11”,”12”)计算出来的位桶数组位置为i = (n -1) & hash = 0位,所谓如图所示:
观察源码663-664可知:
当put第12位时仍不会触发扩容,第13位就会触发resize()扩容机制。
从12位计算出位置为i = (n -1) & hash = 1,所以会放到下标为1的位置上边,但是该位置上边是有元素存在的,所以执行完源码的扩容方法之后会进来这里:
在位于桶1的位置的元素节点的next位置,创建该节点接到后边。
而下面这段代码其实回去从链表的初始节点一直判断其next节点是不是为null,如果next一直不为空直到++binCount >= 7,那么链表将会转化为红黑树。
此时的hashMap结构为:
当key=”13”时,正常逻辑,其应该会接在位置2的后边,也就是这样:
但是,此时size>12阈值了,即
所以会执行一次resize()!!!!也就是扩容!!!
执行的源码如下:
新的容量和新的阈值都翻了一倍,也就是newCap=32,newThr=24
扩容之后,就需要考虑元素迁移的事情了。
元素迁移:
首先是进到了旧的数组不为空的分支,开始元素迁移
看如下源码,我们来进行分析一下:
首先,遍历的旧表的每个下标,首先是数组第0位,也就是
不为空,则先将旧表该位置的链表赋值给e,然后置为null等垃圾回收,
接着判断e有没有后驱节点next,没有的话就表示该下标下边只有一个元素,
开始元素迁移:
其位置为newTab[e.hash & (newCap - 1)],也就是newTab[1568 & 32- 1] = newTab[0],按位与运算结果为0,则迁移至newTab[0]
接着遍历数组下一个下标1,该下标为1也有两个元素,所以:
先将旧表该位置的链表赋值给e,然后置为null等垃圾回收,
接着判断e有没有后驱节点next, 此时该下标下有多个元素且少于8个,所以next不为空,
开始来到源码719行进行元素迁移:
首先定义两个链表的头尾,一个链表用来装与旧表元素位置相同的元素,另一个链表用来装需要重新分配位置的元素。
现在我们知道下标为1的位置有两个元素
首先是当前链表key=”1”的元素e,其(e.hash & oldCap) = 49 & 16不满足==0的条件,所以其位置是需要移动的!即会执行
此时hiHead = Node(K=1,V=1,hash=49) ,同时链表尾部hiTail也指向e
接下来遍历下一个节点,并赋值给e
那么下一个元素会判断其(e.hash & oldCap) = 1569 & 16 = 0 刚好满足==0的条件
则会执行如下代码
此时loHead = Node(K=12,V=12,hash=1569),同时链表尾部loTail也指向e
后边无节点遍历,跳出do…while…
开始执行
元素最终迁移的位置:
最后将loHead放在newTab[1]即在新数组中与旧数组位置相同的地方!!
而hiHead则被放在新的数组newTab[1 + 16]即在旧数组位置基础上再加上旧数组的容量!!
最后,简单画了一下HashMap的存储流程,太晚了就不优雅了,见笑
一篇博客肝了将近三个小时,创作不易,点个赞呗!
HashMap底层详讲相关推荐
- HashMap底层详解
1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端. 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二分查找时间复杂度小,为O(1 ...
- java源码系列:HashMap底层存储原理详解——4、技术本质-原理过程-算法-取模具体解决什么问题
目录 简介 取模具体解决什么问题? 通过数组特性,推导ascii码计算出来的下标值,创建数组非常占用空间 取模,可保证下标,在HashMap默认创建下标之内 简介 上一篇文章,我们讲到 哈希算法.哈希 ...
- hashmap底层源码详解
这里聊一下HashMap: HashMap底层数据结构: HashMap1.7之前数据结构是数组+链表 HashMap1.8之后数据结构加了红黑树(是用来处理hash冲突的) HashMap1.7之前 ...
- 一次性讲清HashMap底层原理!
前言 快速入门 存储:put方法put(key,value) 查询:get方法get(key) java代码如下 import java.util.HashMap; import java.util. ...
- Redis五种基本数据类型底层详解(原理篇)
Redis五种基本数据类型底层详解 详细介绍Redis用到的数据结构 简单动态字符串 SDS和C字符串的区别 总结 链表 字典 哈希表 字典 哈希算法 解决键冲突 rehash(重点) 渐进式reha ...
- 聊聊Java系列-集合之HashMap底层结构原理
前言 由于HashMap在我们的工作和面试中会经常遇到,所以搞懂HashMap的底层结构原理就显得十分有必要了.在JDK1.8之前,HashMap的底层采用的数据结构是数组+链表, ...
- 【Java基础】HashMap原理详解
[Java基础]HashMap原理详解 HashMap的实现 1. 数组 2.线性链表 3.红黑树 3.1概述 3.2性质 4.HashMap扩容死锁 5. BATJ一线大厂技术栈 HashMap的实 ...
- HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别(转)
HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别 文章来源:http://www.cnblogs.com/beatIteWeNerverGiveU ...
- hashmap扩容机制_图文并茂:HashMap经典详解!
点击上方 Java后端,选择 设为星标 优质文章,及时送达 代码中的注解多看几遍,其中HashMap的扩容机制是要必懂知识!结合图片一起理解! 什么是 HashMap? HashMap 是基于哈希表的 ...
最新文章
- IOT(33)---NB-IOT通用物联解决方案
- Linux进程间通信之管道(pipe)、命名管道(FIFO)与信号(Signal)
- 【劲峰论道时空分析技术-学习笔记】1 时空数据和时空变量时空过程和时空机理
- python网格搜索、贝叶斯调参实战
- python中%d_python中%d是什么
- VS2005 安装WTL
- 【史上最全】设计师必备的83个设计资源网站
- 一些不错的酷站欣赏的网站
- 2019 / 3 /24 触摸屏键盘的功能实现
- html5定义页脚的标签,HTML中footer标签的使用方法
- Unity 2D教程 | 骨骼动画:创建动画
- win10设备管理没有android,win10电脑不能识别安卓设备怎么解决?
- postman双击打不开的解决方案
- Postman使用xmysql连接数据库及Handshake inactivity timeout、PROTOCOL SEQUENCE TIMEOUT问题解决
- 分布式链路追踪Jaeger快速入门-01
- 使用Fireworks 8制作网页效果图2-生成网页
- 【是题解】luogu1726上白泽慧音
- 用unity和php实现一个排行榜功能(unity客户端篇)
- 解决DELL PERC H730P mini更换电池BBU后仍然显示FAILED的故障
- 很燃基于掘金量化台的《Python量化交易战新简