HashMap实现原理

HashMap
  1. HashMap基础数据结构:

[外链图片转存失败(img-uBtujeaq-1562295943916)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps1.png)]

  • 如上结构课看出,HashMap主要是有一个链表的形式来存储数据 ,上面Node类和C语言中的结构体很像,如上可以看出HashMap底层由是一个数组结构,数组中的每一项又是一个链表,新建一个HashMap的时候,会初始化数组。数组中的每个元素都是Node类,其中包括了Key,Value,还有对应的下一个节点的地址信息,类似指针。
  1. 提到Hashmap首先要知道的一个是哈希分布和哈希碰撞
  • 对于每个对象X和Y,如果当(且仅当,译者注)X.equals(Y)为false,使得X.hashCode() != Y.hashCode()为true,这样的函数叫做完美Hash函数。下面是完美哈希函数的数学表达.
    • X,∀YS, (h(X)=h(Y))⟺X=Y:S 是所有对象的几何,h为哈希函数。
  • 从上面可以看出,哈希分布主要是通过Hash函数来实现的,但那是hash函数返回的是int类型的数据,也就是能表示的范围 在2^23个数据,当数据量超过这个范围时候吗,就必定会出现两个数据对应同一个Hash值的情况,这种时候就出现了哈希碰撞
  1. 现在解决哈希碰撞的方法一种是开放寻址,一种是分离链接。其他的用于解决Hash冲突的方式,大多基于这两种方法

[外链图片转存失败(img-6dOpBr5h-1562295943917)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps2.jpg)]

  1. 开放寻址是一种解决哈希冲突的方式,当计算出的桶索引的位置被占据时,通过一定的算法,来寻找未被占据的哈希桶(适合数量确定,冲突较少的情况,译者注)。而分离链接则将每一个哈希桶作为一个链表的头结点,当哈希碰撞发生时,仅需在链表中进行储存、查找。这两种方法都有着同样的最坏时间复杂度O(M),但是开放寻址使用连续的空间,因此有着缓存效率的提升。因此当数据量较小时,能够放到系统缓存中时,开放寻址会表现出比分离链接更好的性能。但是当数据量增长时,它的性能就会越差,因为我们无法期待一个大的数组能够得到缓存性能优化。这也是HashMap使用分离链表来解决哈希冲突的原因。此外,开放寻址还有一个弱点。我们调用remove()方法会十分频繁,当我们删除数据时,一个特定的哈希冲突,可能会干扰总体的性能,而分离链表则没有这样的缺点。
  2. HashMap中几个重要参数:
  • Int threshold 所能容纳的key-value的极限
  • Final float loadFactor 负载因子
  • Int modCount 分布式锁标记
  • Int size 数组中node的数量
  • Node[] table 初始化大小是16,loac factor负载因子默认大笑是0.75, threshold是HashMap所能容纳的最大数据量Node 个数, 计算公式 Threshold = length* loacFactor,也就是说数组定义好之后负载因子越大,所能容纳的键值对越多。
  • 由上面公式可以看出,threshold是在loacfactor和length长度允许下最大元素的数量。超过这个容量之后就要resize(扩容)扩容之后的容量是之前的两倍, loadFactor的定义是对空间课时间复杂度的一个权衡,如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值;相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。
  • Size是hashMap中实际存在的node的个数,和table的length还有最大容量threshold是有区别的。
  1. 存在的一个问题,就是不管hash算法多么合理,都避免不了hash被占满的情况,这时候会出现链表过长的情况,在jdk1.8之前,没有对链表过长情况做优化,在jdk1.8版本中,对数据结构做了进一步的优化,引入了红黑树,而当链表过长时候(默认8个)链表就会转成红黑树,利用红黑树的快速增删改查来提高hashMap的性能,其中会用到红黑树的插入、删除、查找等算法。

  2. HashMap中包有四个构造方法,方法如下。

    [外链图片转存失败(img-RaXvv7Sh-1562295943918)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps3.png)]

    1. 第一个构造方法:带两个参数构造方法: 其中initialCapacity 指定HashMap 的总容量,loadFactor指加载因子,表示当HashMap满的时候扩容的依据。

    [外链图片转存失败(img-ywVMSeTR-1562295943918)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps4.png)]

    1. 第二个构造方法:如上,只有一个参数信息,表示HashMap的总容量信息。DEFAULT_LOAD_FACTOR加载因子默认是0.75f

    [外链图片转存失败(img-ZaJbgCrp-1562295943919)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps5.png)]

    1. 第三个构造方法:初始化一个空的HashMap 加载因子也是默认数值0.75f
    2. 第四个构造方法:直接将一个类型相同的HashMap赋值,加载因子还是默认大笑0.75

[外链图片转存失败(img-TFCCb9Tv-1562295943919)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps6.png)]

  1. HashMap实现原理

[外链图片转存失败(img-wS5sbQoD-1562295943919)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps7.png)]

  • Put方法 如上源码中可见,HashMap的put方法
    • 首先对key做hash检查NodeTable是否为空,如果是空的数组,先对数组进行初始化。
    • 第二个判断,HashMaP允许存放key为null和value为null的情况,当key == null时候,HashMap会将这个值放到第一位置
    • 第二个判断,通过对Key的hash值的判断指定hash对应的table中是否存在对应的元素,如果指定hashCode的table索引处值是null表示此处还没有Node节点,接着新建一个Node节点并且初始化KeyValue,next为null。
    • 如果判断hash处存在key碰撞,则以链表的形式存在buckets后面
    • 如果碰撞导致链表过长就把链表转成红黑树,如果bucket满了,那么通过resize重新分配内存空间,如下.

[外链图片转存失败(img-Gw7TX7js-1562295943920)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps8.png)]

  • HashMap中的get方法:
    • 首先检查第一个元素,每次都会先检查第一个元素,如果没有命中,执行下面
    • 通过key获取对应的entry
    • 如果拿到的entry是树,择通过getTreeNode获取
    • 如果拿到的是链表,择通过ke.equalsa(key)查找。
  1. HashMap桶的扩容:
  • HashMap中Node数组的我们称他为Hash桶,当不断的向HashMap中曾加数据到固定的阀值时候会触发扩容,如下代码:

[外链图片转存失败(img-POUBVfUd-1562295943920)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps9.png)]

  • 如上代码:Threshold = DEFAULT_LOAD_FACXTORDEFAULT_INITIAL_CAPACITY = 0.7516 = 12所以当使用默认值初始化HashMap的时候,第一次是12个元素开始触发扩容。扩容之后的容量是原来容量的两倍如下代码:

[外链图片转存失败(img-0ajY75gN-1562295943920)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps10.png)]

  • 如上逻辑,如果扩容之后的容量小于HashMap可承载的最大值 1<<30 (2^30),则扩容到原来的两倍 newTHr = oldThr<<1.就是新建一个Node数组,并且将老数组中的值按照原来的规则重新Hash到新的数组中。
  • 之后的操作在jdk1.7和jdk1.8 中不一样:
    • Jdk1.7中:在新建链表的过程中是使用的单链表的头结点插入方式,旧的数组中数据经过重新计算Hash值,然后放入新的数组中,同一个Hash位置上的新元素总会被放在Hash链表的头部位置。

[外链图片转存失败(img-7uXuveYB-1562295943921)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps11.jpg)]

  • JDK1.8中:
    • 在1.8 中做了一部分优化,其中使用的是2次幂的扩张(原来的2被), 所以元素的位置要么在原来位置,要么在移动2次幂的位置。如下图:

[外链图片转存失败(img-dBRHK5Gd-1562295943921)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps12.jpg)]

  • 图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,其中hash1是key1对应的哈希与高位运算结果。
  • 注意:如上后面的值是hash之后的值比如 hash2: 1111 1111 1111 1111 0000 1111 0001 0101 最后得到的hash是一个int类型,只有四位,所以是最后的四位:0101
  • 当扩容之后,n变为原来的两倍,相当于左移动一位,择变成了1 0101得到之后的结果

[外链图片转存失败(img-prZTu2WQ-1562295943921)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps13.jpg)]

  • 所以扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,可以看看下图为16扩充为32的resize示意图:

[外链图片转存失败(img-tz6hP7I9-1562295943922)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps14.jpg)]

  1. 总结:
  • hashMap工作原理:

    • 通过hash的方法,通过put和get存储和获取对象,存储的位置通过HashCode计算hash得到对应的bucket位置,然后在存储,HashMap会根据当前的node数组占用情况来来自动调整容量,(size的值超过LoadFacotr的值则resize到原来的2倍),
    • 获取对象的时候,我们就将key传给get方法,他调用hashCode计算hash从而buacket位置,并且进一步调用equals方法来确认key是否一直,如果发生碰撞,HashMap通过链表来存储这些元素, 在java8 中,如果一个bucket中碰撞冲突的元素超过了某个限制(8个),择会将原来的链表转换成红黑树来替换,利用红黑树的CURD高性能来提高hashMap的效率。
  • Hash具体实现以及1.8优化:

    • 对于任意给定的对象只要HashCode返回值相同,那么调用方法所计算得到的hash码应该是相同的,然后通过hash和数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,模运算的消耗还是比较大的:具体方法:

[外链图片转存失败(img-SX0pIOwK-1562295943922)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps15.png)]

  • 因为HashMap的长度总是2的N次方,这是HashMap在速度上的一个有事,当length总是2的n次方的时候,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。如下图:

[外链图片转存失败(img-nVdvT13e-1562295943922)(file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml7216\wps16.jpg)]
如上图所示,异或和取摸运算得到的hash值是一样的。

  • String,Integer这样的wrapper类适合做键:
    • 因为String是不可变的类型,也是Final类型,而且重写了equals和hashCode方法,其他的包装类也有这个特点,因为在计算的时候需要用到hashCode方法,而且在get的时候需要用到queals方法,这种情况下需要key对应的hash值是前后不变的这样可以准确找到对应的位置,并且还有线程安全的优点。所以键对象重写hashCode和equals方法是很有必要的。
  • 参考文章:
    • https://blog.csdn.net/richard_jason/article/details/53887222
    • https://blog.csdn.net/c139352227/article/details/47861815
    • https://blog.csdn.net/super_me_jason/article/details/79741298

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310630.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

做好技术管理,你必须要跨越的4道槛

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份读者群里有不少刚开始做管理的技术人&#xff0c;很多都和我谈过他们的困惑。总结下来主要是不知道继续晋升需要培养哪方面的能力。技术经理其实是技术人最难做好的管理岗&#xff0c;原因主要有两方面&#…

图的最小生成树和最短路径算法思路总结(Prim,Kruskal,Dijkstra,Floyd)

带权无向图—>最小生成树算法—>Prim算法: 思路: 首先&#xff0c;我们先设置两个集合&#xff0c;U_{}&#xff1a;一个用来放最小生成树的顶点&#xff0c;T_{}&#xff1a;一个用来放最小生成树的边。选取最开始的点V_0&#xff0c;将V_0放入U_{}中&#xff0c;得到U_…

玩转控件:对Dev的GridControl控件扩展

缘由一切实现来源于需求&#xff0c;目的在于不盲目造轮子&#xff0c;有小伙伴儿在看了《玩转控件:对Dev中GridControl控件的封装和扩展》文章后&#xff0c;私信作者说&#xff0c;因公司业务逻辑比较复杂&#xff0c;展示字段比较多&#xff0c;尤其网格列表控件展示数据太多…

二叉排序树(搜索树BST)-详解结点的删除

在二叉排序树中删除一个结点时&#xff0c;需保证删除后的二叉树仍然是二叉排序树。为讨论方便&#xff0c;假定被删除结点为p&#xff0c;其双亲结点为f。删除的过程可按下述的两种情况分别处理。 在这里我们用红色三角形表示我们要删除的结点&#xff0c;蓝色表示我们要改变指…

java调优方法,jvm监控工具

graph LR A-->B性能概述 程序性能表现形式 执行速度&#xff1a;程序响应速度&#xff0c;总耗时是否足够短内存分配&#xff1a;内存分配是否合理&#xff0c;是否过多消耗内存或者存在泄漏启动时间&#xff1a;程序运行到可以正常处理业务需要的时间负载承受能力 性能测…

那位五十多岁的创业者给我的启示!

作者&#xff1a;邹溪源&#xff0c;长沙资深互联网从业者&#xff0c;架构师社区特邀嘉宾&#xff01;一我曾经提到过最终改行从事美缝行业的老w&#xff0c;他靠自己的“不够努力”&#xff0c;最终离开了行业。但是&#xff0c;这个世界其实有点讽刺。在沉迷于安逸小日子的老…

平衡二叉树(AVL树)-详解平衡调整

平衡调整: (注意&#xff1a;平衡调整只是平衡调整&#xff0c;没有进行结点的插入) LL型调整: (带阴影的小框表示插入的结点) 代码如下: AVLNode *AVLTree::LL_Rotate(AVLNode *a) {AVLNode *b;b a->lchild;a->lchild b->rchild;b->rchild a;a->bf b-&g…

初识消息队列/RabbitMQ详解

欢迎大家阅读《朝夕Net社区技术专刊》我们致力于.NetCore的推广和落地&#xff0c;为更好的帮助大家学习&#xff0c;方便分享干货&#xff0c;特创此刊&#xff01;很高兴你能成为忠实读者&#xff0c;文末福利不要错过哦&#xff01;今天来给大家分享关于消息队列的内容&…

zookeeper理解

Zookeeper简介 Zookeeper的数据模型 层次化的目录结构&#xff0c;命名符合常规文件系统规范每个节点在zookeeper中叫做znode&#xff0c;并且有一个唯一的路径标识节点znode可以包含数据和子节点&#xff0c;但是EPHEMERAL类型的节点不能有子节点Znode中的互刷可以有多个版本…

那些年,在MSRA实习过的女孩,现在都怎么样了?

编者按&#xff1a;我们用两周时间回访了五位在 MSRA 实习过的女孩&#xff0c;她们也都是当年“实习派”的主人公。我们本想在更长的时间维度下&#xff0c;看一看 MSRA 给予她们的改变。然而超乎预期的是&#xff0c;在她们身上&#xff0c;坚持的力量比改变更强。变与不变&a…

[Qt入门] QPushButton创建

1.创建一个QPushButton(按钮): #include "mywidget.h" #include <QPushButton>myWidget::myWidget(QWidget *parent): QWidget(parent) {//创建一个按钮QPushButton * btn new QPushButton;btn->show();//show以顶层方式弹出窗口控件//让btn对象 依赖在my…

Zookeeper理解---ZAB协议

ZAB协议 Zookeeper并不是完全采用Paxos算法&#xff0c;而是使用了一种称为Zookeeper Atomic Broadcast&#xff08;ZAB&#xff0c;Zookeeper原子消息广播协议&#xff09;作为数据一致性的核心算法&#xff0c;依据此算法来实现分布式数据一致性的解决。他是一种特别为Zooke…

【最强VSCode】之管理MySql数据库

(梅花香自苦寒来)你没有看错&#xff0c;就是用VSCode来管理MySql数据库&#xff0c;我也是第一次听说&#xff0c;感谢群管理DX小伙伴&#xff0c;三人行必有我师焉。话不多说&#xff0c;直接开张&#xff0c;VSCode还是很不错的&#xff0c;以后多多分享插件吧。1、你平时是…

[Qt入门]QMainWindow创建

代码如下: #include "mainwindow.h" #include <QMenuBar> #include <QToolBar> #include <QDebug> #include <QPushButton> #include <QStatusBar> #include <QLabel> #include <QDockWidget> #include <QTextEdit>…

Zookeeper--ZAB与Paxos算法联系与区别

ZAB与Paxos算法的联系与区别 两者联系 两者都存在一个类似于Leader的进程角色&#xff0c;由其负责协调多个Follower进程的运行Leader进程会等待超过半数的Follower做出正确的反馈后&#xff0c;才会将一个提案进行提交在ZAB协议&#xff0c;每个Proposal中都包含一个epoch值…

[Flags]标识的Enum不能使用Html.GetEnumSelectList方法

在使用Asp.Net Core MVC写程序时&#xff0c;对用户类型做了如下定义&#xff1a;namespace ManufacturingExecutionSystemCore.Enums {public enum UserType{[Description("无身份人员")][Display(Name "无身份人员")]UnidentifiedPerson0x00,[Descripti…

[Qt入门]模态和非模态对话框创建

模态对话框创建: #include "mainwindow.h" #include "ui_mainwindow.h" #include<QDialog> #include <QDebug>MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow) {ui->setupUi(this);connect(ui->…

Zookeeper实践与应用- Canal

基于MySql BinLog的增量订阅和消费组件&#xff1a;Canal Cancal是阿里13年1月开源的一个基于MySql数据库Binlog实现的增量订阅和消费的组件。项目取名Canal取自管道的英文单词&#xff0c;流转的医生&#xff0c;是一个定位于基于MySql数据库Binlog增量日志来实现数据库镜像&…

你遇到的面试官是「伯乐」吗?

之前写了一篇应应聘者视角的「面试中要注意的点」&#xff0c;《聊聊面试的事&#xff08;应聘方&#xff09;》。这次再来一篇面试官视角的。如果你不是面试官&#xff0c;也没关系。所谓“知己知彼&#xff0c;方能百战百胜”&#xff0c;了解一下面试官在面试时的侧重点&…

[Qt入门]消息对话框创建

错误对话框: //错误对话框QMessageBox::critical(this,"critical","错误");信息对话框: //信息对话框QMessageBox::information(this,"info","信息");提问对话框: //提问对话框//1.QMessageBox::question(this,"ques",&quo…