【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底层详讲相关推荐

  1. HashMap底层详解

    1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端. 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二分查找时间复杂度小,为O(1 ...

  2. java源码系列:HashMap底层存储原理详解——4、技术本质-原理过程-算法-取模具体解决什么问题

    目录 简介 取模具体解决什么问题? 通过数组特性,推导ascii码计算出来的下标值,创建数组非常占用空间 取模,可保证下标,在HashMap默认创建下标之内 简介 上一篇文章,我们讲到 哈希算法.哈希 ...

  3. hashmap底层源码详解

    这里聊一下HashMap: HashMap底层数据结构: HashMap1.7之前数据结构是数组+链表 HashMap1.8之后数据结构加了红黑树(是用来处理hash冲突的) HashMap1.7之前 ...

  4. 一次性讲清HashMap底层原理!

    前言 快速入门 存储:put方法put(key,value) 查询:get方法get(key) java代码如下 import java.util.HashMap; import java.util. ...

  5. Redis五种基本数据类型底层详解(原理篇)

    Redis五种基本数据类型底层详解 详细介绍Redis用到的数据结构 简单动态字符串 SDS和C字符串的区别 总结 链表 字典 哈希表 字典 哈希算法 解决键冲突 rehash(重点) 渐进式reha ...

  6. 聊聊Java系列-集合之HashMap底层结构原理

    前言           由于HashMap在我们的工作和面试中会经常遇到,所以搞懂HashMap的底层结构原理就显得十分有必要了.在JDK1.8之前,HashMap的底层采用的数据结构是数组+链表, ...

  7. 【Java基础】HashMap原理详解

    [Java基础]HashMap原理详解 HashMap的实现 1. 数组 2.线性链表 3.红黑树 3.1概述 3.2性质 4.HashMap扩容死锁 5. BATJ一线大厂技术栈 HashMap的实 ...

  8. HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别(转)

    HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别 文章来源:http://www.cnblogs.com/beatIteWeNerverGiveU ...

  9. hashmap扩容机制_图文并茂:HashMap经典详解!

    点击上方 Java后端,选择 设为星标 优质文章,及时送达 代码中的注解多看几遍,其中HashMap的扩容机制是要必懂知识!结合图片一起理解! 什么是 HashMap? HashMap 是基于哈希表的 ...

最新文章

  1. IOT(33)---NB-IOT通用物联解决方案
  2. Linux进程间通信之管道(pipe)、命名管道(FIFO)与信号(Signal)
  3. 【劲峰论道时空分析技术-学习笔记】1 时空数据和时空变量时空过程和时空机理
  4. python网格搜索、贝叶斯调参实战
  5. python中%d_python中%d是什么
  6. VS2005 安装WTL
  7. 【史上最全】设计师必备的83个设计资源网站
  8. 一些不错的酷站欣赏的网站
  9. 2019 / 3 /24 触摸屏键盘的功能实现
  10. html5定义页脚的标签,HTML中footer标签的使用方法
  11. Unity 2D教程 | 骨骼动画:创建动画
  12. win10设备管理没有android,win10电脑不能识别安卓设备怎么解决?
  13. postman双击打不开的解决方案
  14. Postman使用xmysql连接数据库及Handshake inactivity timeout、PROTOCOL SEQUENCE TIMEOUT问题解决
  15. 分布式链路追踪Jaeger快速入门-01
  16. 使用Fireworks 8制作网页效果图2-生成网页
  17. 【是题解】luogu1726上白泽慧音
  18. 用unity和php实现一个排行榜功能(unity客户端篇)
  19. 解决DELL PERC H730P mini更换电池BBU后仍然显示FAILED的故障
  20. 很燃基于掘金量化台的《Python量化交易战新简

热门文章

  1. 树莓派配置无线网络(补充) 【for_wind】
  2. python爬虫拖动验证码_python爬虫学习:验证码之滑动验证码
  3. Onvif协议PTZ服务规范(一)PTZ Service Specification
  4. c语言实验设备管理系统设计作业,C语言课程设计实验设备管理系统设计
  5. json字符串转json对象(前端json字符串转json对象)
  6. java SAXReader
  7. Java实例——线程
  8. js实现查找两个相同字符串之间的最长子字符串长度
  9. Ubuntu快捷键——终端
  10. SFTP命令常用操作