一、HashMap的原理

众所周知,HashMap是用来存储Key-Value键值对的一种集合,这个键值对也叫做Entry,而每个Entry都是存储在数组当中,因此这个数组就是HashMap的主干。
HashMap数组中的每一个元素的初始值都是NULL

注:文末有福利!

 

1.Put方法的实现原理

HaspMap的一种重要的方法是put()方法,当我们调用put()方法时,比如hashMap.put("Java",0),此时要插入一个Key值为“Java”的元素,这时首先需要一个Hash函数来确定这个Entry的插入位置,设为index,即 index = hash("Java"),假设求出的index值为2,那么这个Entry就会插入到数组索引为2的位置。

 

但是HaspMap的长度肯定是有限的,当插入的Entry越来越多时,不同的Key值通过哈希函数算出来的index值肯定会有冲突,此时就可以利用链表来解决。
其实HaspMap数组的每一个元素不止是一个Entry对象,也是一个链表的头节点,每一个Entry对象通过Next指针指向下一个Entry对象,这样,当新的Entry的hash值与之前的存在冲突时,只需要插入到对应点链表即可。

 

需要注意的是,新来的Entry节点采用的是“头插法”,而不是直接插入在链表的尾部,这是因为HashMap的发明者认为,新插入的节点被查找的可能性更大。

2.Get方法的实现原理

get()方法用来根据Key值来查找对应点Value,当调用get()方法时,比如hashMap.get("apple"),这时同样要对Key值做一次Hash映射,算出其对应的index值,即index = hash("apple")。
前面说到的可能存在Hash冲突,同一个位置可能存在多个Entry,这时就要从对应链表的头节点开始,一个个向下查找,直到找到对应的
Key值,这样就获得到了所要查找的键值对。
例如假设我们要找的Key值是"apple":

 

第一步,算出Key值“apple”的hash值,假设为2。
第二步,在数组中查找索引为2的位置,此时找到头节点为Entry6,Entry6的Key值是banana,不是我们要找的值。
第三步,查找Entry6的Next节点,这里为Entry1,它的Key值为apple,是我们要查找的值,这样就找到了对应的键值对,结束。

二、HashMap的深入思考

上面所说的就是HashMap的基本原理,可以总结出HashMap的3个要素为:hash函数、数组、链表,如下图:

 

接下来对于HaspMap还有很多深入的问题,比如:
1.HashMap默认的初始长度是多少?为什么这么规定?
2.高并发情况下,HashMap会出现死锁吗?
3.Java8中,HashMap有怎样的优化?
下面开始说明这几个问题:

1.HashMap的长度

1.HaspMap的默认初始长度是16,并且每次扩展长度或者手动初始化时,长度必须是2的次幂。之所以是16,是为了服务于从Key值映射到index的hash算法。前面说到了,从Key值映射到数组中所对应的位置需要用到一个hash函数:index = hash("Java");

那么为了实现一个尽量分布均匀的hash函数,利用的是Key值的HashCode来做某种运算。因此问题来了,如何进行计算,才能让这个hash函数尽量分布均匀呢?

一种简单的方法是将Key值的HashCode值与HashMap的长度进行取模运算,即 index = HashCode(Key) % hashMap.length,但是,但是!这种取模方式运算固然简单,然而它的效率是很低的, 而且,如果使用了取模%, 那么HashMap在容量变为2倍时, 需要再次rehash确定每个链表元素的位置,浪费了性能。
因此为了实现高效的hash函数算法,HashMap的发明者采用了位运算的方式。那么如何进行位运算呢?可以按照下面的公式:

index = HashCode(Key) & (hashMap.length - 1);

接下来我们以Key值为“apple”的例子来演示这个过程:

  1. 计算“apple”的hashcode,结果为十进制的3029737,二进制的101110001110101110 1001。

  2. HashMap默认初始长度是16,计算hashMap.Length-1的结果为十进制的15,二进制的1111。

  3. 把以上两个结果做 与运算,101110001110101110 1001 & 1111 = 1001,十进制是9,所以 index=9。

可以看出来,hash算法得到的index值完全取决与Key的HashCode的最后几位。这样做不但效果上等同于取模运算,而且大大提高了效率。

那么回到最初的问题,初始长度为什么是16或者2的次幂?如果不是会怎么样?

我们假设HaspMap的初始长度为10,重复前面的运算步骤:

 

单独看这个结果,表面上并没有问题。我们再来尝试一个新的HashCode 101110001110101110 1011 :

 

然后我们再换一个HashCode 101110001110101110 1111 试试 :

 

这样我们可以看到,虽然HashCode的倒数第二第三位从0变成了1,但是运算的结果都是1001。也就是说,当HashMap长度为10的时候,有些index结果的出现几率会更大,而有些index结果永远不会出现(比如0111)!

所以这样显然不符合Hash算法均匀分布的原则。

而长度是16或者其他2的次幂,Length - 1的值的所有二进制位全为1(如15的二进制是1111,31的二进制为11111),这种情况下,index的结果就等同于HashCode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。这也是HashMap设计的玄妙之处。

2.HashMap的死锁

我们知道HashMap是非线程安全的,那么原因是什么呢?

由于HashMap的容量是有限的,如果HashMap中的数组的容量很小,假如只有2个,那么如果要放进10个keys的话,碰撞就会非常频繁,此时一个O(1)的查找算法,就变成了链表遍历,性能变成了O(n),这是Hash表的缺陷。

为了解决这个问题,HashMap设计了一个阈值,其值为容量的0.75,当HashMap所用容量超过了阈值后,就会自动扩充其容量。

在多线程的情况下,当重新调整HashMap大小的时候,就会存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历。如果条件竞争发生了,那么就会产生死循环了。

具体发生死锁的过程可以参考这篇文章:疫苗:JAVA HASHMAP的死循环

3.HashMap的优化

关于Java8中对于HashMap的优化,可以参考下面两篇文章:
Java 8系列之重新认识HashMap

HashMap源码分析(JDK1.8)

最后,给大家推荐一个良心公众号【IT资源社】:

本公众号致力于免费分享全网最优秀的视频资源,学习资料,面试经验等,前端,PHP,JAVA,算法,Python,大数据等等,你想要的这都有

IT资源社-QQ交流群:601357554

微信搜索公众号:ITziyuanshe 或者扫描下方二维码直接关注,

 
 

里面基本什么资料都有,基础到进阶到项目实战,如果觉得不够还可以加群跟群主要,最重要的是全部免费!

面试必问---HashMap原理分析的更多相关文章

  1. Java面试必问之Hashmap底层实现原理(JDK1.7)

    1. 前言 Hashmap可以说是Java面试必问的,一般的面试题会问: Hashmap有哪些特性? Hashmap底层实现原理(get\put\resize) Hashmap怎么解决hash冲突? ...

  2. linux驱动工程面试必问知识点

    linux内核原理面试必问(由易到难) 简单型 1:linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些? 2:linux中内存划分及如何使用?虚拟地址及物理地址的概念及彼此之间的转化, ...

  3. 互联网公司面试必问的mysql题目(下)

    这是mysql系列的下篇,上篇文章地址我附在文末. 什么是数据库索引?索引有哪几种类型?什么是最左前缀原则?索引算法有哪些?有什么区别? 索引是对数据库表中一列或多列的值进行排序的一种结构.一个非常恰 ...

  4. 互联网公司面试必问的mysql题目(上)

    又到了招聘的旺季,被要求准备些社招.校招的题库.(如果你是应届生,尤其是东北的某大学,绝对福利哦) 介绍:MySQL是一个关系型数据库管理系统,目前属于 Oracle 旗下产品.虽然单机性能比不上or ...

  5. 一线大厂Java面试必问的2大类Tomcat调优

    一.前言 最近整理了 Tomcat 调优这块,基本上面试必问,于是就花了点时间去搜集一下 Tomcat 调优都调了些什么,先记录一下调优手段,更多详细的原理和实现以后用到时候再来补充记录,下面就来介绍 ...

  6. TCP的分分合合(面试必问)

    TCP连接与断开 目录 TCP连接与断开 前言 握手 挥手 最后 前言 相信面试过的小伙伴对这个话题应该不陌生,算是面试必问了,三次握手,四次挥手,以及其中的一些衍生问题. TCP/IP(Transm ...

  7. 互联网公司面试必问的Redis题目

    Redis是一个非常火的非关系型数据库,火到什么程度呢?只要是一个互联网公司都会使用到.Redis相关的问题可以说是面试必问的,下面我从个人当面试官的经验,总结几个必须要掌握的知识点. 介绍:Redi ...

  8. 【面试必问】python实例方法、类方法@classmethod、静态方法@staticmethod和属性方法@property区别

    [面试必问]python实例方法.类方法@classmethod.静态方法@staticmethod和属性方法@property区别 1.#类方法@classmethod,只能访问类变量,不能访问实例 ...

  9. 面试必问:JVM类加载机制详细解析

    前言 在Java面试中,简历上有写JVM(Java虚拟机)相关的东西,JVM的类加载机制基本是面试必问的知识点. 类的加载和卸载 JVM是虚拟机的一种,它的指令集语言是字节码,字节码构成的文件是cla ...

随机推荐

  1. Oracle定时任务小案例

    需求简述 一个数据表中包含此数据的录入时间,此数据的初始状态是有效,五天后系统自动置该数据的状态为无效. 方案 写一个存储过程,用于更新字段(改状态): 写一个job,用于定时执行存储过程: 方案逻辑 ...

  2. 进一步理解阿贾克斯(Ajax)

    一.ajax简介 1.Asynchronous JavaScript and XML(异步的Javascript和XML) 2.是一种在无需重新加载整个网页的情况下能够更新部分网页的技术. 二.aja ...

  3. 【经验随笔】Restlet Client发送XML格式参数消息

    背景介绍 Restlet Client是一款模拟客户端发送http请求的chrome插件,可以作为驱动调试后台程序,个人觉得比POSTMAN好用.CXF是一款发布restful服务的开源组件. 编写r ...

  4. C++学习-8

    1.注意:函数指针前面*,&都是一样的没啥实际意义,除了把实例化函数块的时候,需要指针或者引用修饰    cout << typeid(my1.show).name() <& ...

  5. syskey——让你的电脑更加安全

    我之前介绍过一个绕过系统登录密码的工具kon-boot,今天介绍的就是可以防止这个工具的方法,也能让你的电脑更加的安全. 这个方法也是我在Youtube上看见的一个方法,还是不错. 方法: win+R ...

  6. 集合中存的是引用,分析一道容易混淆的Java面试题

    我们自定义的类是以引用的形式放入集合,如果使用不当,会引发非常隐蔽的错误.就拿我经常问到的一个面试题来说明这个知识点. 第一步,我们定义一个Car类型的类,其中只有一个int类型id属性. 第二步,创 ...

  7. Office远程代码执行漏洞(CVE-2017-11882)复现

    昨晚看到的有复现的文章,一直到今天才去自己复现了一遍,还是例行记录一下. POC: https://github.com/Ridter/CVE-2017-11882/ 一.简单的生成弹计算器的doc文 ...

  8. python作业01

    1.编译型语言在应用源执行前,需要先通过编译将程序源代码翻译为可被硬件平台直接运维的二进制机器码,编译好的二进制执行文件仅能在对应平台运行.因此不依赖解释器,执行效率高,跨平台性差.常见的编译型语言: ...

  9. spring boot jsp页面

    相关内容访问: http://www.cnblogs.com/zj0208/p/5985698.html

  10. 大数据 --> 分布式文件系统HDFS的工作原理

    分布式文件系统HDFS的工作原理 Hadoop分布式文件系统(HDFS)是一种被设计成适合运行在通用硬件上的分布式文件系统.HDFS是一个高度容错性的系统,适合部署在廉价的机器上.它能提供高吞吐量的数 ...