那些年一起看过的HashMap面试题

前言

我们在日常的工作学习中,都会常常用到HashMap,记得在校招面试中,也是基本上多多少少会问到一些关于HashMap的知识点,因此我们对于HashMap也不能仅仅停留在使用get、put操作存取数据,接下来就看看HashMap有哪些常见的问题。

了解数据结构中的HashMap么?讲讲你对HashMap的认识

这种应该就属于比较基本的问题,根据这个问题来引出之后的问题。
HashMap由数组+链表的形式组成,是一个用于存储key-value键值对的集合,在Java7中叫做Entry,在Java8中叫做node,差不多就是这个样子
在这里插入图片描述
这个数组就是HashMap的主干了。在向HashMap中存放数据的时候,会根据存放的key调用hashCode方法得到hash值,进而得到我们存放的数据应当放的位置。

如果要存放的两个对象的hash值相同,那该怎么办?

刚才我们知道hashMap是由数组与链表组成的,这个时候就需要我们的链表登场了。
hashcode相同,那么他们所要存放在hashMap中的桶位的位置也就相同,但是一个位置只能存放一个数据,此时就会通过头插法或者尾插法形成链表。也就是介个样子:
在这里插入图片描述

你刚才提到了头插法与尾插法,看来是对这两个有些了解,那么说下什么是头插法,什么是尾插法

无论是头插法,还是尾插法,都是新的节点插入到原有的节点上形成链表。在Java8之前是头插法,头插法就是我们之后新添加进的值会取代原有的值,原有的值会被推到链表中。而Java8以及Java8之后就舍弃了头插法,采用尾插法。

既然已经Java8之前都是用的头插法,为什么还要改用尾插法呢?

举个栗子:假如HashMap的一个桶位中有两个元素:

在这里插入图片描述

其中key1指向key2。如果hashMap中存放的元素过多的时候,就会触发扩容resize,扩容之后hashMap的容量就增成为原先的两倍。原先hashMap中的元素就会重新进行hash计算得到新的hash值来插入到新的hashMap中,如果采用了头插法,那么肯定是先插入key1,后插入key2,因为key2是后插入的,所以会将key1顶到后面,也就是下面的情况:
在这里插入图片描述
发现了没有,采用头插法就改变了原有的顺序,而且key1是要指向key2的,这样就会造成链表循环。如果采用尾插法,后插入的元素仍然放到后面,这样的话就能够保证原有的元素顺序不改变。
在这里插入图片描述

既然不会出现链表循环,那么是不是说hashMap可以用到多线程情况下呢?

这个是不可以的,虽然不会出现死循环,但是hashMap在使用put与get对值进行操作的时候没有加锁,也就是说put值之后,get的值不一定是原值,因此,如果要想保证线程安全的话,可以使用hashTable、concurrentHashMap。(这两个数据结构也应当好好看看,有的时候会与hashMap一块问到)

不错不错,这些内容还是了解的挺好的,但是刚刚你提到了扩容,那么扩容是什么?它的过程又是怎么样的呢?

关于扩容还是很重要的,小伙伴们一定要对此多多了解。
这里我们需要知道两个变量:
Capacity:hashMap长度。
LoadFactor:负载因子,默认0.75。
因为我们的hashMap的默认长度是16(也要记住,有的可能会问到hashMap的默认长度是多少),但是在我们使用的时候,一个hashMap不可能只存放十来个数据。hashMap中定义了负载因子为0.75,当hashMap中的元素达到:hashMap的长度负载因子(0.75)的时候就会触发扩容。比如我们默认的长度是16,也就是说,当hashMap中的元素达到160.75=12的时候,就会进行扩容。
扩容的时候首先会创建一个空的数组,这个空的数组的长度是原先数组长度的2倍,然后将原先的hashMap中的元素进行一遍hash计算,根据hash值来插入到对应的桶位中。因为新的数组的长度与原先的数组长度不同,所以元素不能直接复制过去。

默认长度为什么是16?负载因子为什么是0.75?长度设为12可不可以

面试官,你的问题可真多。
我们看下hashMap中是怎么定义默认长度的:
在这里插入图片描述可以看到,官方的回答就是长度必须是2的次幂,位运算要比直接的算数计算效率要高一点,同时也能尽量使得放入的值分布均匀。
以默认的长度16为例,我们知道在计算机中数值都是以二进制来进行存储的,16的二进制就是10000。
hashMap中计算放进去的值的index位置计算公式是index=(n-1)&hash
在这里插入图片描述因此(16-1)的二进制就是1111,这样hash值与上1111,最终要放的位置还是与它本身的hash值有关,只要hash值是均匀,那么它所存放的位置就会是均匀分布的。如果长度为12,那么11的二进制就是1011,这样的话就会对原先的hash值造成干扰,可能原先的hash值是均匀的,但是经过运算之后就不均匀了,因此不能是12,必须是2的次幂。
至于默认是16,其实多少都是可以的,能够保证是2的整数次幂就OK了。
负载因子设置为0.75是为了提高空间利用率和减少查询成本的折中方法,根据泊松分布,0.75的情况下碰撞最小。

在Jdk8与Jdk8之前相比,除了由头插法换为尾插法,你还知道有其他的变化么?

这个是hashMap中又一个很重要的点,一定要多多了解。

我们知道,在jdk1.8之前hashMap是由数组+链表组成的,在jdk1.8及其以后,hashMap由原先单纯的数组+链表改为了当链表长度c超过7,达到8,并且hashMap表的长度大于64的时候会由链表转换为红黑树,当红黑树的长度小于6个的时候,会由红黑树变为链表。
我们知道链表的时间复杂度是O(n),而红黑树的时间复杂度是O(logn),既然红黑树优于链表,那么为什么不直接使用红黑树,还大费周章的链表转红黑树,红黑树转链表?我们可以看下hashMap的源码:
在这里插入图片描述 什么意思呢?原来树节点所需空间是普通节点的两倍,当节点较少的时候,虽然从时间复杂度的角度上来看,红黑树优于链表,但是所占空间较高,所以说当节点较多的情况下才去使用红黑树。
其实在理想的情况下,受hash值的影响,链表中的节点遵循泊松分布,链表中的节点超过7,达到8的概率不到千万分之一,因此大部分情况下,hashMap使用的还是链表。
有一点需要注意:链表转换为红黑树的情况,不仅仅只需要链表长度达到8,还需要hashmap的长度超过64,
在这里插入图片描述可以看到如果hashMap的长度小于MIN_TREEIFY_CAPACITY的时候,会先去进行扩容操作,如果大于等于 MIN_TREEIFY_CAPACITY了,才回去由链表转换红黑树。而 MIN_TREEIFY_CAPACITY的大小就是:
在这里插入图片描述
我看大部分博客都只是简单说下链表长度达到8就转换红黑树了,而没有提前面的判断条件,这一点还是需要注意的。

总结

hashMap作为我们面试最常问的知识点,一定要多多了解,不清楚的地方多查查,毕竟hashMap是能够和面试官多扯一会的,哈哈。
如果各位觉得有用,可以点下赞,收藏下,万一忘记了,还可以翻出来看一下。文中有不对的地方欢迎各位指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值