前言
本文是个人对Hashmap的一些个人见解,主要通过使用hashmap put的一些代码来阐述其底层实现原理,在面试中也会经常会用到,如有不对的地方望大家指正。

(1)先描述一下hashmap的一个底层数据结构:
Hashmap底层是由数组和链表结合实现的。如下图:

hashmap数据结构
hashMap数据结构
其中每个table[x](或table[x].next…)都是一个node,都是一个插入的元素

Node实体类为:

static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;

}
可以看到有四个参数:hash(hash值)、key(我们平常put的key)、value(put的value)、next(hashMap数据结构图中的.next,也就是记录链表中每个元素的后继元素)

1.创建hashmap对象:Map<String,Object> map = new HashMap<>();
创建一个map对象,对象中的一些参数属性被赋予默认值,主要有参考容量(capacity)、扩容阈值(threshold)和负载因子(loadFactor),下面简单介绍一下这些参数属性的作用和关系。

(1)参考容量(capacity)
用来作为创建map对象中Node[]数组的初始长度(容量)的参考,默认为16。

可以自己指定长度,指定方式为:Map<String,Object> map = new HashMap<>(capacity);//capacity的值就是你要指定的长度

这里之所以说是参考,是因为hashmap内部有一个机制:创建map对象中Node[]数组的初始长度必须要是2的n次方(原因在2.(3)),当你设置长度是23的时候,hashmap会把初始长度设置成32。因为23在16(2的4次方)到32(2的5次方)之间,取最大的数32。

(2) 扩容阈值(threshold)和负载因子(loadFactor)
hashmap在新增元素的过程中,如果达到扩容阈值,就会扩大Node[]数组的长度

其默认值为:参考容量 * 负载因子

而负载因子的默认值为0.75,可以修改但是不建议修改。

2.首次put:map.put(“haha”,“haha的值”);
将"haha的值"插入到"haha"的键的node中。

这里可以带几个问题:

元素要想把值插到数组+链表的结构中的话,首先要接触数组,然后再接触链表

那我们怎么知道要把元素放在数组的哪个位置(数组下标怎么得到)?

什么情况下会把元素放在链表里?

(1)计算出"haha"的hash值
int hash = hash(“haha”);
(2)判断数组是否为空
判断Node[]数组是否为空,为空的话按照初始长度开辟数组空间

(3)对hash值作减一与运算得到数组下标
直接算出来的hash值可能非常大,不可能直接当作数组下标的。对此hashmap的设计者有自己的解决方案:求余

也就是:index = hash值 % 数组长度

这样的话index的值永远都在数组长度之内,也就可以作为数组下标了

但是这样做有一个缺点:效率低,于是hashmap的设计者把求余改成了一个效率更高的运算:减一与运算

也就是:index = hash值 & (数组长度-1)

为什么这样得出来的index也在数组长度之内呢?可以看下例子(由于是位运算,需要把hash值和数组长度分解成二进制,这样看的更清楚,假设它两二进制只有八位):

数组长度: 0001 0000

数组长度-1: 0000 1111

hash值: 1101 0101

与操作: 0000 0101

可以看到,数组长度-1后,前四位变成了0,跟hash值作与操作之后,hash值前四位不管是什么,都会变成0,而后四位由于对方都是1,因此得以保留下来。这样得到最后的结果永远都不会超过数组长度。

这里必须要满足一个前提条件:数组长度必须要是2的n次方。因为这样才能保证数组长度-1之后,前面为0,后面为1。

(4)把值赋给对应的node
数组下标拿到了,要插入的位置也就基本确定了。在插入之前,hashmap会去判断当前数组位置上有没有元素,由于我们这是第一次插入,因此这里就是直接插入元素。

这里插入的方式很简单,就是把node的四大参数(hash值、key、value、next)赋给当前数组位置上的node。由于是位置上第一个元素,后继没有链表元素,next的值就是null。

(5)插入后操作
插入之后,hashmap的全局变量:size,也就是已有元素数量,加一,然后看下有没有大于扩容阈值,如果大的话就要扩容。

(6)最终效果(这里设"haha"算出的index为2)
haha put结果
haha put结果
3.put不同key不同index的值:map.put(“heihei”,“heihei的值”);
这里的过程和2.一样,最终结果为(这里设"heihei"算出的index为0):

heihei put结果
heihei put结果
4.put相同key的值:map.put(“haha”,“haha另一个值”);
这里得到数组下标值之后,发现位置上已经有元素了,这种情况就叫做哈希碰撞

这时候,就要看看这个位置上的元素是不是同一个key,是的话就覆盖,不是的话就追加到后继链表——这就是链表的作用

怎么判断是不是同一个key呢?

(1)hash值是否相等
首先要看看计算出来的hash值是否相等,这里算出来的是相等的。

(2)两个key是否相等
hash值相等,两个key不一定相等,因为算hash值需要调用hashCode()方法,这个方法我们是可以自己复写的,可能会出现很多不同结果相等的情况。

所以我们需要通过"=="和equals()来判断两个key是否相等。这里算出来的是相等的。

为什么不直接判断是否相等,而还要先判断hash值?

因为hash值不相等,那肯定不是同一个key。算hash值相对来说速度快点,有这个先把关会大大提高效率,而不是上来就==和equals。

(3)覆盖原值
判断是同一个key,那就直接覆盖原值就行了,最终结果为:

haha另一个值 put结果
haha另一个值 put结果
5.首次put不同key相同index的值:map.put(“haha2”,“haha2的值”)
这里假设"haha2"算出来的index和"haha"一样。

在这个前提下,自然就会出现哈希碰撞。这时候的过程也是和4.一样,区别在于在比对key的时候,发现两个key的hash值不相等,也就不是同一个key

之后自然会把这个元素插到"haha"元素后面形成链表

插入的方式很简单,就是把"haha2"元素赋给"haha"的next,"haha2"的next为null

最终结果为:

haha2 put结果
haha2 put结果
这里插入之前会进行一些判断,比如是否是红黑树等等,后面的情况会细讲。

6.继续put不同key相同index的值:map.put(“haha3”,“haha3的值”)
这里假设"haha3"算出来的index和"haha"一样。

过程和5.一样,区别在于在比对的时候,会遍历当前数组位置上链表的所有元素,如果发现相同key则覆盖,没有相同key就追加到链表后面。

最终结果为:

haha3 put结果
haha3 put结果
7.put到链表转成红黑树:map.put(“haha8”,“haha8的值”)
这里假设"haha8"算出来的index和"haha"一样。

这里假设链表上的元素已经有7个了。

前面我们发现,随着put的元素越来越多,哈希碰撞的次数也越来越频繁,导致链表会越来越长,这样每次put要遍历的元素也越来越多,会让hashmap的性能越来越差。

(1)链表转成红黑树
于是红黑树的解决方案就出现了。红黑树是一种能让遍历的效率更高的一种数据结构。

当链表的元素数量达到8的时候,当前链表自动转成红黑树的形式存储,能大大提升遍历效率。

(2)扩容机制
除了红黑树,还有没有什么解决方案可以提高性能呢?

有——扩容机制

之所以链表越来越长,是因为哈希碰撞的次数太频繁了。那我们可以通过降低这种次数来提升性能。扩容可以做到这一点。

扩容是增加数组的长度,这样我们就有更多的空间去分配链表的位置,哈希碰撞的次数就会少很多。

类似于我们去餐厅吃饭,位置越多我们吃上饭的概率就越大,而不用老是等位(遍历)。

hashmap的扩容过程简单来说就是新建一个长度为原来2倍(因为要满足2的n次方)的数组Node[],然后把旧数组+链表上的元素全部转移到新的Node[]上

链表长度为8的时候一定会转成红黑树吗?

不一定,如下代码:

if (tab == null || (n = tab.length) < 64)
resize();
else
//转成红黑树
可以看到会先做判断,满足扩容条件会先扩容,不扩容再转成红黑树。

(3)最终效果("haha8"put进去对应链表转成了红黑树)
haha8 put结果
haha8 put结果

8.总结
Hashmap 执行put的时候有以下几个步骤:

(1)先计算出对应key的hash值,然后去判断当前Node[]数组是不是为空,为空就新建,不为空就对hash值作减一与运算得到数组下标
(2)然后会判断当前数组位置有没有元素,没有的话就把值插到当前位置,有的话就说明遇到了哈希碰撞
(3)遇到哈希碰撞后,就会看下当前链表是不是以红黑树的方式存储,是的话,就会遍历红黑树,看有没有相同key的元素,有就覆盖,没有就执行红黑树插入
(4)如果是普通链表,则按普通链表的方式遍历链表的元素,判断是不是同一个key,是的话就覆盖,不是的话就追加到后面去
(5)当链表上的元素达到8个的时候,如果不满足扩容条件,链表会转换成红黑树;如果满足扩容条件,则hashmap会进行扩容,把容量扩大到以前的两倍

HashMap put原理详解(基于jdk1.8)相关推荐

  1. Java集合篇:Hashtable原理详解(JDK1.8)

    (本文使用的源码基于JDK1.8的) 一.Hashtable的基本方法: 这部分参考博客:https://blog.csdn.net/chenssy/article/details/22896871 ...

  2. OSPFv2原理详解(基于RFC2328)+配置介绍+RFC2328翻译

    个人认为,理解报文就理解了协议.通过报文中的字段可以理解协议在交互过程中相关传递的信息,更加便于理解协议. 虽然路由器自身可以对协议做一些独特的配置,但是报文仍然是协议的核心.例如,OSPF的完全末节 ...

  3. Java集合篇:HashMap原理详解(JDK1.7及之前的版本)

    (本文有关HashMap的源码都是基于JDK1.6的) 摘要: HashMap是Map族中最为常用的一种,也是 Java Collection Framework 的重要成员.本文首先给出了 Hash ...

  4. Java集合篇:HashMap原理详解(JDK1.8)

    概述 JDK 1.8对HashMap进行了比较大的优化,底层实现由之前的"数组+链表"改为"数组+链表+红黑树",本文就HashMap的几个常用的重要方法和JD ...

  5. hashmap实现原理详解

    要毕业了,hashmap是面试官非常喜欢问的问题,经常会碰到:来说说hashmap的实现原理,hashmap怎么get.put的,行吧,简单总结一下. static final int DEFAULT ...

  6. HashMap 原理详解

    一.HashMap的原理详解 首先我们要知道什么是哈希表以及它的结构.在介绍哈希表之前我们需要了解并且掌握数组.链表以及红黑树的结构以及特点. 1.我们先来看一下HashMap的使用 public c ...

  7. HashMap原理详解(基于jdk1.8)

    HashMap原理详解(基于jdk1.8) HashMap原理详解,有兴趣的同学可以看下.有错误的地方也希望大佬们能指点下. HashMap的内部存储是一个数组(bucket),数组的元素Node实现 ...

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

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

  9. Java HashMap的实现原理详解

    HashMap是Java Map类型的集合类中最常使用的,本文基于Java1.8,对于HashMap的实现原理做一下详细讲解. (Java1.8源码:http://docs.oracle.com/ja ...

最新文章

  1. Python 运行 Python hello.py 出错,提示: File stdin , line 1
  2. sysdig案例分析 - 哪些文件正在被进程访问
  3. Share Point 开发系列之一:开发方式的选择
  4. 清除默认的内边距与外边距
  5. mysql close conn_mysql CloseConnection问题
  6. cdoj 1328 卿学姐与诡异村庄 Label:并查集 || 二分图染色
  7. [Linux] Ubuntu13.04 搭建OK6410-A开发板的开发环境
  8. PCIE的DMA和PIO介绍
  9. 260道网络安全工程师面试题(附答案)
  10. 微软重新评估收购雅虎提议
  11. C++【STL】【string类的使用】
  12. ipcam rtsp流生成 mp4( 附源码)
  13. 推荐系统——矩阵分解
  14. Android 模仿flabby bird游戏开发
  15. 第二届“强网”拟态防御国际精英挑战赛落幕,29支国内外精英队伍未能突破拟态防御,赛宁网安靶场平台完美支撑BWM新赛制.
  16. JAVA高考加油,给高考学子加油打气的祝福语
  17. 最简单安全有效的防盗技术和防脱机外挂技术。研发部门可以借鉴使用。
  18. atom与mysql,Atom
  19. 微信dat文件用什么软件打开方式_如何打开微信dat文件方式方法有哪些
  20. 搭建Tomcat集群详解

热门文章

  1. java项目之Bank银行代码
  2. 什么是Python脚本?
  3. h5页面预览pdf文件_H5移动端在线浏览pdf文件,推荐插件TouchPDF
  4. VS Code解决Go相关工具无法安装问题
  5. es6删除数组某项_es6删除数组元素或者对象元素的方法介绍(代码)
  6. 坐标轨迹计算_机器人的轨迹规划与自动导引
  7. 集成运算放大电路实验报告_模电总结:第三章、集成运算放大电路
  8. mysql infobright 缺点_infobright、mongodb优劣以及适用范围
  9. python控制台输入代码_Python实现控制台输入密码的方法
  10. oracle的and怎么用,Oracle的BITAND的方法使用