2020年Java面试208题 019-请描述HashMap的底层原理

时间:2021年10月15日星期五 多云

1. 题目

大家好,我是小崔爱读书,继续Java面试208题的第19题,今天面试官的问题是:请说说HashMap的底层原理。

2. 知识点分析

HashMap是Java开发中非常频繁使用的集合,哪怕是Java新手也应该可以熟练使用HashMap,也知道这个东西里面存储的就是键值对,但要说到底层原理和逻辑,可能很多程序员还真不太能将清楚,我认为要想做到深层次的剖析HashMap,得知道以下这几点内容:

  1. HashMap类的每个键值对都是一个对象, 类型是HashMap的内部类 Node 。

  2. HashMap类的内部将其实是所有HashMap.Node 对象的数组。

  3. HashMap类将每个HashMap.Node对象Key值进行Hash计算,以决定其在数组中的位置。

  4. 一个数组中的位置如果有多个Node对象,将以链表方式管理。

基本差不多了,这几个知识点理解清楚了,HashMap在你面前就是赤条条的了,毫无秘密可言。

现在我分别来解释一下。

2.1. HashMap的键值对是一个对象

所有的Java程序员都知道,HashMap是管理键值对的,这个键值对具体是怎么管理的呢?

HashMap内部类有一个Node类,就是用来存储键值对的。

我们总说键值对,键值对,感觉好像是一对儿东西,其实不是,其实在HashMap内部这是一个东西,就是HashMap中,每个键值对其实是一个对象。

这种管理方式其实也不是HashMap这样设计的,而是Map接口就已经这样定义了,在Map接口内部有一个Entry接口,就是用来存储键值对的。HashMap的Node类,就是实现了Map的Entry接口。

2.2. HashMap将所有的Node对象,即所有的键值对存储为一个数组

刚刚说了,一个键值对就是一个HashMap.Node对象,那么HashMap就是管理多个HashMap.Node对象的集合了。

那么,如果既然每个键值对就是一个对象,那HashMap其实也就是存储了多个对象。

那么,这个特性与数组是不是一样的?想想数组是存储啥的?数组就是存储多个类型相同的对象的嘛。HashMap只是比较特殊,他存储的对象的类型要求必须是Node类型的。

所以,HashMap在实现的时候,就将所有的键值对对象,存储为一个数组了。

这个数组的长度是根据键值对的数量而动态调整的,但不是简单的一对一的,也就是说,并不是有10个键值对,这个数组长度就是10。而是初始化的时候数组长度为1,每次会扩容一倍,具体的扩容时机,我等会儿再说。

2.3. 键值对放在数组的位置,是根据Key值进行Hash计算而得到的

键值对的数组并不是普通的数组那样,所有的元素都顺序的排放,而是根据Key值进行Hash计算,最后得到一个位置,然后将键值对放在这个数组位置。

2.4. HashMap的Hash冲突问题

既然是计算Key值得到的键值对的位置索引,那么就出现了一个问题,如果两个Key值计算后位置索引是相同的怎么办呢?这就叫Hash冲突。

这个问题造成的结果是,我们每个数组中只有一个位置放一个键值对对象,但现在突然来了两个对象,这就冲突了。

2.5. Hash冲突的时候,就以链表方式管理多个键值对

当出现Hash冲突问题的时候,HashMap将多个键值对组成链表,数组中只存储链表的头对象。需要查找的时候就根据Key值的Hash计算到数组的位置,然后再遍历链表,找到正确的键值对。

2.6.  HashMap扩容机制和扩容时机

HashMap会出现Hash冲突,这个很正常。从概率来说,HashMap的数组越长,出现Hash冲突的概率就越低。因此HashMap就提供了一个扩容机制,即当键值对太多的时候,就把数组在弄大一点,这就降低了Hash冲突的可能性。

什么时候扩容呢?HashMap有一个算法,一般来说是当数组的长度小于 0.75倍的键值对个数,然后又出现了一次Hash冲突的时候,就会启动扩容了。

2.7. 当链表长度超过8个的时候将采用红黑树的方式存储

就是说,当某个数组索引下的键值对太多了,链表就太长了。

当长度超过8个的时候,就会转为红黑树了。

因为链表的查找速度慢,而红黑树的性能要强的太多了,因此转为红黑树存储了。至于红黑树的概念,这里不做扩展解释,以后有机会再讲。

3. 模拟面试

好的,接下来,我就演示一下模拟面试。

面试官您好。

首先,HashMap是实现的Map接口,是Java最重要的集合之一。

HashMap存储的是键值对,每个键值对都存储为一个 Node对象,这个Node类是HashMap的内部类。

多个键值对,即Node对象,在HashMap中主要以数组的方式存储。也就是说HashMap内部首先开辟一个数组控件,然后将键值对放到这个数组中。

但是HashMap对数组的管理并不是普通的顺序摆放的方式,就是不同于线性数组对元素的管理,第1个元素放到第1个位置,第2个索引放到第2个位置。HashMap对键值对的Key进行Hash计算,计算后得到一个整数值,然后根据整数取余计算得到一个索引值,然后把键值对放到该索引值对应的数组中的位置。这样的管理方式在放入对象的时候有点儿繁琐,也不是很繁琐,一点点的小计算而已,但取数据的时候就会非常快。因为线性数组的话,找一个元素只能遍历,但HashMap则是简单计算后直接得到索引位置,这个速度要快的多,数组越大,元素越多,速度提升越明显。

了解了这个存储方式,就能力理解为什么HashMap中的键值对的顺序是无序的,为啥存储方式并不是按照插入的先后顺序排列的。

但这就有一个小麻烦出现了,因为键值对的Hash值是可能重复的,就造成了两个键值对要放到数组中相同的索引位置的问题。这就是Hash冲突问题。

HashMap解决Hash冲突问题采用的是链表的方式,当两个键值对在一个位置的时候,将两个键值对做出链表,然后将链表首位的键值对放入数组索引中。这样,数组索引位置还是只有一个键值对,但键值对下面还有链表找到其他的键值对。可以想象HashMap的结构有点儿像是一只蜈蚣,一节节的都是键值对,当某一节的键值对数量超过1个了,就放到这一节的腿上。

这里又有一个小问题,链表的速度也不是很快啊,是啊,我们知道链表中插入数据很快,但如果查找数据速度并不快,链表中的元素少的话还不是问题,如果元素多的话,性能就很是问题了。

HashMap的数组某个索引位置如果键值对太多,那么链表就太长了,那检索元素的速度又慢了。

为了解决这个问题,HashMap采用了链表转红黑树的机制,当某个链表长度超过8个的时候,就将链表转为红黑树,红黑树的查找速度比链表要快的多。转换的时机就是看链表的长度是否超过了8个,一旦超过8个就转换。

由于HashMap主要以数组的方式存储键值对,这就存在数组长度太小的情况,这就需要在合适的时机进行扩容。原则上就是元素太多,数组长度太短,就要扩容了。一般来说,HashMap是判断如果数组的长度小于元素的数量乘以0.75,并且新加入一个键值对出现了Hash冲突,这时候就会扩容。

一旦扩容,HashMap就会重新组织数组,重新处理链表,重新整理红黑树,这个过程就会在一定程度上造成性能问题和系统开销。

好的,这就是我对HashMap底层的理解。

4. 总结

好的,本期就到这里,希望对您有所帮助,我们下期再见。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小崔爱读书

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值