【面试知识点】一文带你深入了解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个,是否一定会树化?

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

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

 

相关文章
|
1月前
|
存储 分布式计算 大数据
HBase分布式数据库关键技术与实战:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入剖析了HBase的核心技术,包括数据模型、分布式架构、访问模式和一致性保证,并探讨了其实战应用,如大规模数据存储、实时数据分析及与Hadoop、Spark集成。同时,分享了面试经验,对比了HBase与其他数据库的差异,提出了应对挑战的解决方案,展望了HBase的未来趋势。通过Java API代码示例,帮助读者巩固理解。全面了解和掌握HBase,能为面试和实际工作中的大数据处理提供坚实基础。
83 3
|
1月前
|
SQL 分布式计算 监控
Sqoop数据迁移工具使用与优化技巧:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入解析Sqoop的使用、优化及面试策略。内容涵盖Sqoop基础,包括安装配置、命令行操作、与Hadoop生态集成和连接器配置。讨论数据迁移优化技巧,如数据切分、压缩编码、转换过滤及性能监控。此外,还涉及面试中对Sqoop与其他ETL工具的对比、实际项目挑战及未来发展趋势的讨论。通过代码示例展示了从MySQL到HDFS的数据迁移。本文旨在帮助读者在面试中展现Sqoop技术实力。
118 2
|
1月前
|
监控 负载均衡 Cloud Native
ZooKeeper分布式协调服务详解:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入剖析ZooKeeper分布式协调服务原理,涵盖核心概念如Server、Client、ZNode、ACL、Watcher,以及ZAB协议在一致性、会话管理、Leader选举中的作用。讨论ZooKeeper数据模型、操作、会话管理、集群部署与管理、性能调优和监控。同时,文章探讨了ZooKeeper在分布式锁、队列、服务注册与发现等场景的应用,并在面试方面分析了与其它服务的区别、实战挑战及解决方案。附带Java客户端实现分布式锁的代码示例,助力提升面试表现。
420 2
|
1月前
|
数据采集 消息中间件 监控
Flume数据采集系统设计与配置实战:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入探讨Apache Flume的数据采集系统设计,涵盖Flume Agent、Source、Channel、Sink的核心概念及其配置实战。通过实例展示了文件日志收集、网络数据接收、命令行实时数据捕获等场景。此外,还讨论了Flume与同类工具的对比、实际项目挑战及解决方案,以及未来发展趋势。提供配置示例帮助理解Flume在数据集成、日志收集中的应用,为面试准备提供扎实的理论与实践支持。
68 1
|
1月前
|
XML 分布式计算 监控
Oozie工作流管理系统设计与实践:面试经验与必备知识点解析
【4月更文挑战第9天】本文详述了Oozie工作流管理系统的核心概念,包括安装配置、Workflow XML、Action、Coordinator和Bundle XML定义。此外,讨论了工作流设计实践,如监控调试、自动化运维,并对比了Oozie与其他工作流工具的差异。文中还分享了面试经验及解决实际项目挑战的方法,同时展望了Oozie的未来发展趋势。通过学习,读者能提升Oozie技术能力,为面试做好充分准备。
36 0
|
1月前
|
消息中间件 监控 大数据
Kafka消息队列架构与应用场景探讨:面试经验与必备知识点解析
【4月更文挑战第9天】本文详尽探讨了Kafka的消息队列架构,包括Broker、Producer、Consumer、Topic和Partition等核心概念,以及消息生产和消费流程。此外,还介绍了Kafka在微服务、实时数据处理、数据管道和数据仓库等场景的应用。针对面试,文章解析了Kafka与传统消息队列的区别、实际项目挑战及解决方案,并展望了Kafka的未来发展趋势。附带Java Producer和Consumer的代码示例,帮助读者巩固技术理解,为面试做好准备。
46 0
|
1月前
|
监控 Java 应用服务中间件
Spring Boot 源码面试知识点
【5月更文挑战第12天】Spring Boot 是一个强大且广泛使用的框架,旨在简化 Spring 应用程序的开发过程。深入了解 Spring Boot 的源码,有助于开发者更好地使用和定制这个框架。以下是一些关键的知识点:
43 6
|
1月前
|
Java 程序员
Java this关键字详解(3种用法),Java程序员面试必备的知识点
Java this关键字详解(3种用法),Java程序员面试必备的知识点
|
5天前
|
存储 网络协议 编译器
【干货总结】Linux C/C++面试知识点
Linux C/C++基础与进阶知识点,不仅用于面试,平时开发也用得上!
|
13天前
|
消息中间件 存储 缓存
面试题--HashMap和TreeMap的区别和应用场景有啥区别?
然后底层调用key的hashCode()方法得出hash值; 过哈希表哈希算法,将hash值转换成数组的下标(注1),下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有值。此时,就会拿着key和链表上每个节点的key进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖,如果最终长度大于8就会转成红黑树,红黑树插入;
17 3