【面试知识点】一文带你深入了解HashMap

简介: 【面试知识点】一文带你深入了解HashMap

HashMap的底层原理:

1.7 数组+链表

1.8 数组+链表+红黑树

 

面试官:HashMap的存储原理:

在数组里面,我们存着Key-Value这样的实例

在java7中叫做Entry,在java8当中叫做Node

在插入的时候,他会根据key的hash去计算一个index(下标),然后存储

每个节点(Ebtry、Node),包括了hash值(通过hash计算出来的)、key的值、Value的值以及next(指向下一个节点)

面试官:在我们节点插入链表的时候,是怎么插入的?

在java8之前,我们使用的是头插法,如下图所示:

 

现在我们发生hash冲撞了,我们会将key:刘的的插入到key:黄的上面:如下:

 

但这样的话,我们没办法进行查询呀,所以我们会再一次进行操作:

 

在java8之后,这种头插法被尾插法所替代。

 

面试官:为什么会被尾插法所替代呢?

第一个是因为我们在java8中,引入了红黑树,我们当链表的长度大于8个时,开始转变红黑树而当红黑树的节点个数小于6个(默认值)以后,又开始使用链表。我们在进行尾插法的时候,还能够遍历一遍size(),来判断要不要进行树的转变

第二个就是:避免多线程死锁

 

面试官:HashMap的扩容机制

HashMap的扩容机制主要和负载因子和当前长度有关

  • Capacity:HashMap当前长度。
  • LoadFactor:负载因子,默认值0.75f

如果当前存储的数量 > 负载因子 * 当前长度,就进行扩容,扩容是怎么样来扩的呢?

分为两步

  • 扩容:创建一个新的Entry空数组,长度是原数组的2倍。
  • ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。

面试官:为什么要重新Hash呢,我直接赋值过去不行吗?

 

我们来看HashMap的Hash方法,Hash方法是:

Hash的公式---> index = HashCode(Key) & (Length - 1)

扩容之前&8,扩容之后&16,肯定就不一样了

关于链表闭环这个问题:

为了避免链表成环的产生(这也是HashMap不支持多线程的原因)

 

我们如果多个线程的话,第一个线程B---A

 

但是我们第二个线程才刚刚跑,A---->B的,这时候就会发生链表的成环(这里的叙述应该不太明确,可以请教一下面试官并顺便舔一波)

在Java8中,我们引进了红黑树,我们使用头插会改变链表上的顺序,但使用尾插法的话,不会改变我们的顺序

Java7在多线程操作HashMap时可能引起死循环,原因是扩容转移后前后链表顺序倒置,在转移过程中修改了原来链表中节点的引用关系。

Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。

面试官:那是不是意味着Java中我们就可以吧HashMap用在多线程了呢?

   我认为即使不会出现死循环,但是通过源码看到put/get方法都没有加同步锁,多线程情况最容易出现的就是:无法保证上一秒put的值,下一秒get的时候还是原值,所以线程安全还是无法保证。

(这里可能就会扯到多线程了,以及死锁、操作系统等)

面试官:HashMap的默认大小是多少?

在Java1.8中,默认大小为16, 而且还给我们特别注释了出来

 

面试官:那为啥是16呢?或者 Hash为什么不直接取模运算呢?

我们刚刚说到,在进行hash计算时:

Hash的公式---> index = HashCode(Key) & (Length - 1)

重点在后面的(Length - 1),我们可以想:我们进行Hash的意义是什么?

我们想让他映射到我们的数组下标对吧,我们取默认大小16的话,写成2进制减去1,看一下:0000 1111

我们回顾一下关于&的操作:只有都为1或者都为0,结果才为1

所以,我们可以看到,不论你的HashCode(Key)是什么,最后一定会在0000~1111之间的

 

面试官:为啥我们重写equals方法的时候需要重写hashCode方法呢?

   在Java中,所有的对象都是继承Object类的Ojbect类中有两个方法equals、hashCode,这两个方法都是用来比较两个对象是否相等的。在未重写equals方法我们是继承了object的equals方法,那里的 equals是比较两个对象的内存地址,显然我们new了2个对象内存地址肯定不一样

  • 对于值对象,==比较的是两个对象的值
  • 对于引用对象,比较的是两个对象的地址

HashMap是通过key的hashCode去寻找index的,那index一样就形成链表了,index都可能是2,在一个链表上的。

我们去get的时候,他就是根据key去hash然后计算出index,找到了2,那我怎么找到具体的呢?

equals!是的,所以如果我们对equals方法进行了重写,建议一定要对hashCode方法重写,以保证相同的对象返回相同的hash值,不同的对象返回不同的hash值。

不然一个链表的对象,你哪里知道你要找的是哪个,到时候发现hashCode都一样,这不是完犊子嘛。

 

面试官:处理HashMap线程安全的场景?

面试官,在这样的场景,我们一般都会使用HashTable或者CurrentHashMap,但是因为前者的并发度的原因基本上没啥使用场景了,所以存在线程不安全的场景我们都使用的是CorruentHashMap。

HashTable我看过他的源码,很简单粗暴,直接在方法上锁,并发度很低,最多同时允许一个线程访问,currentHashMap就好很多了,1.7和1.8有较大的不同,不过并发度都比前者好太多了。

 

面试官:HashMap的 hash(Obeject k)方法中为什么在调用 k.hashCode()方法获得hash值后,为什么不直接对这个hash进行取余,而是还要将hash值进行右移和异或运算?

   如果HashMap容量比较小而hash值比较大的时候,哈希冲突就容易变多。基于HashMap的indexFor底层设计,假设容量为16,那么就要对二进制0000 1111(即15)进行按位与操作,那么hash值的二进制的高28位无论是多少,都没意义,因为都会被0&,变成0。所以哈希冲突容易变多。那么hash(Obeject k)方法中在调用 k.hashCode()方法获得hash值后,进行的一步运算:h^=(h>>>20)^(h>>>12);有什么用呢?首先,h>>>20和h>>>12是将h的二进制中高位右移变成低位。其次异或运算是利用了特性:同0异1原则,尽可能的使得h>>>20和h>>>12在将来做取余(按位与操作方式)时都参与到运算中去。综上,简单来说,通过h^=(h>>>20)^(h>>>12);运算,可以使k.hashCode()方法获得的hash值的二进制中高位尽可能多地参与按位与操作,从而减少哈希冲突。

 

 

面试官:jdk1.8引入红黑树后,如果单链表节点个数超过8个,是否一定会树化?

不一定,它会先去判断是否需要扩容(即判断当前节点个数是否大于扩容的阈值),如果满足扩容条件,直接扩容,不会树化,

因为扩容不仅能增加容量,还能缩短单链表的节点数,一举两得。

 

相关文章
|
3月前
|
缓存 NoSQL Java
校招 Java 面试常见知识点及实战案例全解析
本文全面解析了Java校招面试中的常见知识点,涵盖Java新特性(如Lambda表达式、、Optional类)、集合框架高级应用(线程安全集合、Map性能优化)、多线程与并发编程(线程池配置)、JVM性能调优(内存溢出排查、垃圾回收器选择)、Spring与微服务实战(Spring Boot自动配置)、数据库与ORM框架(MyBatis高级用法、索引优化)、分布式系统(分布式事务、缓存应用)、性能优化(接口优化、高并发限流)、单元测试与代码质量(JUnit 5、Mockito、JaCoCo)以及项目实战案例(电商秒杀系统、社交消息推送)。资源地址: [https://pan.quark.cn/s
113 4
|
3月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
182 3
|
3月前
|
存储 设计模式 算法
校招 Java 面试常见知识点汇总及备考指南
本文全面解析校招Java面试常见知识点,涵盖Java基础、集合框架、多线程并发、JVM等内容。从面向对象特性(封装、继承、多态)到数据类型与包装类,再到字符串处理和关键字用法,逐一剖析。集合框架部分深入讲解List、Set、Map接口及其常用实现类的特性和应用场景。多线程章节探讨线程创建、同步机制及线程池的使用。JVM部分聚焦内存区域、垃圾回收机制和类加载过程。结合实际案例,助你轻松应对校招面试!资源地址:[点此获取](https://pan.quark.cn/s/14fcf913bae6)。
93 0
|
8月前
|
存储 算法 Java
面试必备!一文搞懂HashMap如何优雅处理哈希冲突
大家好,我是小米,一个积极的程序员。今天聊聊Java面试中的常见问题——“HashMap是怎么解决哈希冲突的?”。通过一个小故事,我们了解到HashMap使用链地址法(JDK 1.8前)和红黑树(JDK 1.8后)来处理哈希冲突。链地址法用链表存储冲突的元素,而红黑树在链表长度超过8时启用,提升查找效率。希望这个讲解能帮助你更好地理解HashMap的工作原理。欢迎留言讨论,关注我的公众号“软件求生”,获取更多技术干货!
242 3
|
9月前
|
存储 Java 索引
HashMap高频面试题,让你掌握青铜回答与王者级回答,你值得拥有
《HashMap高频面试题,让你掌握青铜回答与王者级回答,你值得拥有》介绍了HashMap的实现原理、数据存储、哈希冲突处理及扩容机制。文章通过对比JDK 1.7和JDK 1.8版本,详细解析了不同版本中的链表与红黑树结构、Entry与Node的区别,以及put()方法的具体实现。特别指出JDK 1.8引入红黑树优化查询性能,并采用尾插法避免多线程环境下的链表环问题。负载因子和扩容机制确保了HashMap在不同场景下的高效运行。
108 2
|
12月前
|
Web App开发 前端开发 Linux
「offer来了」浅谈前端面试中开发环境常考知识点
该文章归纳了前端开发环境中常见的面试知识点,特别是围绕Git的使用进行了详细介绍,包括Git的基本概念、常用命令以及在团队协作中的最佳实践,同时还涉及了Chrome调试工具和Linux命令行的基础操作。
「offer来了」浅谈前端面试中开发环境常考知识点
|
11月前
|
存储 Java 程序员
Java面试加分点!一文读懂HashMap底层实现与扩容机制
本文详细解析了Java中经典的HashMap数据结构,包括其底层实现、扩容机制、put和查找过程、哈希函数以及JDK 1.7与1.8的差异。通过数组、链表和红黑树的组合,HashMap实现了高效的键值对存储与检索。文章还介绍了HashMap在不同版本中的优化,帮助读者更好地理解和应用这一重要工具。
421 5
|
11月前
|
Android开发
Android面试高频知识点(1) 图解Android事件分发机制
Android面试高频知识点(1) 图解Android事件分发机制
|
11月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
11月前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
Android面试高频知识点(4) 详解Activity的启动流程
126 3

热门文章

最新文章