HashMap,看完秒懂!(HashMap底层实现原理、HashMap面试题)

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

我们都知道数组长度是有限的,在有限的长度里面我们使用哈希,哈希本身就存在概率性,就是”帅丙“和”丙帅“我们都去hash有一定的概率会一样,就像上面的情况我再次哈希”丙帅“极端情况也会hash到一个值上,那就形成了链表。

每一个节点都会保存自身的hash、key、value、以及下个节点,我看看Node的源码。

说到链表我想问一下,你知道新的Entry节点在插入链表的时候,是怎么插入的么?

java8之前是头插法,就是说新来的值会取代原有的值,原有的值就顺推到链表中去,就像上面的例子一样,因为写这个代码的作者认为后来的值被查找的可能性更大一点,提升查找的效率。

但是,在java8之后,都是所用尾部插入了。

为啥改为尾部插入呢?

这!!!这个问题,面试官可真会问!!!还好我饱读诗书,不然死定了!

有人认为是作者随性而为,没啥luan用,其实不然,其中暗藏玄机

首先我们看下HashMap的扩容机制:

帅丙提到过了,数组容量是有限的,数据多次插入的,到达一定的数量就会进行扩容,也就是resize。

什么时候resize呢?

有两个因素:

  • Capacity:HashMap当前长度。

  • LoadFactor:负载因子,默认值0.75f。

怎么理解呢,就比如当前的容量大小为100,当你存进第76个的时候,判断发现需要进行resize了,那就进行扩容,但是HashMap的扩容也不是简单的扩大点容量这么简单的。

扩容?它是怎么扩容的呢?

分为两步

  • 扩容:创建一个新的Entry空数组,长度是原数组的2倍。

  • ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。

为什么要重新Hash呢,直接复制过去不香么?

卧槽这个问题!有点知识盲区呀!

1x1得 1 1x2 得 2 …. 有了,我想起来敖丙那天晚上在我耳边的话了:假如我年少有为不自卑,懂得什么是珍贵,那些美梦没给你,我一生有愧….什么鬼!

小姐姐:是因为长度扩大以后,Hash的规则也随之改变。

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

原来长度(Length)是8你位运算出来的值是2 ,新的长度是16你位运算出来的值明显不一样了。

扩容前:

扩容后:

说完扩容机制我们言归正传,为啥之前用头插法,java8之后改成尾插了呢?

卧槽,我以为她忘记了!居然还是被问到了!

我先举个例子吧,我们现在往一个容量大小为2的put两个值,负载因子是0.75是不是我们在put第二个的时候就会进行resize?

2*0.75 = 1 所以插入第二个就要resize了

现在我们要在容量为2的容器里面用不同线程插入A,B,C,假如我们在resize之前打个短点,那意味着数据都插入了但是还没resize那扩容前可能是这样的。

我们可以看到链表的指向A->B->C

Tip:A的下一个指针是指向B的

因为resize的赋值方式,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置,在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。

就可能出现下面的情况,大家发现问题没有?

B的下一个指针指向了A

一旦几个线程都调整完成,就可能出现环形链表

如果这个时候去取值,悲剧就出现了——Infinite Loop。

诶卧槽,小伙子难不倒他呀!

小伙子有点东西呀,但是你都都说了头插是JDK1.7的那1.8的尾插是怎么样的呢?

因为java8之后链表有红黑树的部分,大家可以看到代码已经多了很多if else的逻辑判断了,红黑树的引入巧妙的将原本O(n)的时间复杂度降低到了O(logn)。

Tip:红黑树的知识点同样很重要,还是那句话不打没把握的仗,限于篇幅原因,我就不在这里过多描述了,以后写到数据结构再说吧,不过要面试的仔,还是要准备好,反正我是经常问到的。

使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。

就是说原本是A->B,在扩容后那个链表还是A->B

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

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

那是不是意味着Java8就可以把HashMap用在多线程中呢?

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

小伙子回答得很好嘛,这都被你回答道了,面试这么多人都不知道头插和尾插,还是被你说出来了,可以可以。

面试官谬赞啊,要不是你这样美若天仙的面试官面试我,我估计是想不起来了。

我*,你套近乎?

小姐姐抿嘴一笑,小子你offer有了,耶稣都带不走你,我说的!

那我问你HashMap的默认初始化长度是多少?

我记得我在看源码的时候初始化大小是16

你那知道为啥是16么?

卧*,这叫什么问题啊?他为啥是16我怎么知道???你确定你没逗我?

我努力回忆源码,不知道有没有漏掉什么细节,以前在学校熬夜看源码的一幕幕在脑海里闪过,想起那个晚上在操场上,跟我好了半个月的小绿拉着我的手说:你就要当爸爸了。

等等,这都是什么鬼,哦哦哦,想起来了!!!

在JDK1.8的 236 行有1<<4就是16,为啥用位运算呢?直接写16不好么?

我再次陷入沉思,疯狂脑暴,叮!

有了!

面试官您好,我们在创建HashMap的时候,阿里巴巴规范插件会提醒我们最好赋初值,而且最好是2的幂。

这样是为了位运算的方便,位与运算比算数计算的效率高了很多,之所以选择16,是为了服务将Key映射到index的算法。

我前面说了所有的key我们都会拿到他的hash,但是我们怎么尽可能的得到一个均匀分布的hash呢?

是的我们通过Key的HashCode值去做位运算。

我打个比方,key为”帅丙“的十进制为766132那二进制就是 10111011000010110100

我们再看下index的计算公式:index = HashCode(Key) & (Length- 1)

15的的二进制是1111,那10111011000010110100 &1111 十进制就是4

之所以用位与运算效果与取模一样,性能也提高了不少!

那为啥用16不用别的呢?

因为在使用是2的幂的数字的时候,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。

只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。

这是为了实现均匀分布

哟小家伙,知道的确实很多,那我问你个问题,为啥我们重写equals方法的时候需要重写hashCode方法呢?

你能用HashMap给我举个例子么?

这都能被他问到,还好我看了敖丙的系列呀,不然真的完了!!!

但是我想拖延点时间,只能故做沉思,仰望天空片刻,45°仰望天空的样子,说实话,我看到面试官都流口水了!可惜我是他永远得不到的男人,好了不装逼了。

我想起来了面试官!

因为在java中,所有的对象都是继承于Object类。Ojbect类中有两个方法equals、hashCode,这两个方法都是用来比较两个对象是否相等的。

在未重写equals方法我们是继承了object的equals方法,那里的 equals是比较两个对象的内存地址,显然我们new了2个对象内存地址肯定不一样

  • 对于值对象,==比较的是两个对象的值

文末

我将这三次阿里面试的题目全部分专题整理出来,并附带上详细的答案解析,生成了一份PDF文档

  • 第一个要分享给大家的就是算法和数据结构

网易严选Java开发三面面经:HashMap+JVM+索引+消息队列

  • 第二个就是数据库的高频知识点与性能优化

网易严选Java开发三面面经:HashMap+JVM+索引+消息队列

  • 第三个则是并发编程(72个知识点学习)

网易严选Java开发三面面经:HashMap+JVM+索引+消息队列

  • 最后一个是各大JAVA架构专题的面试点+解析+我的一些学习的书籍资料

网易严选Java开发三面面经:HashMap+JVM+索引+消息队列

还有更多的Redis、MySQL、JVM、Kafka、微服务、Spring全家桶等学习笔记这里就不一一列举出来

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
bMWk-1714643280459)]

  • 第二个就是数据库的高频知识点与性能优化

[外链图片转存中…(img-i2fiEeAl-1714643280459)]

  • 第三个则是并发编程(72个知识点学习)

[外链图片转存中…(img-RDjxBCCG-1714643280460)]

  • 最后一个是各大JAVA架构专题的面试点+解析+我的一些学习的书籍资料

[外链图片转存中…(img-449Y5eG0-1714643280460)]

还有更多的Redis、MySQL、JVM、Kafka、微服务、Spring全家桶等学习笔记这里就不一一列举出来

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值