HashMap面试题知识大全

HashMap常见面试题:

1.HashMap的底层数据结构?
2. HashMap的存取原理?
3. Java7和Java8的区别?
4. 为啥会线程不安全?
5. 有什么线程安全的类代替么?
6. 默认初始化大小是多少?为啥是这么多?为啥大小都是2的幂?
7. HashMap的扩容方式?负载因子是多少?为什是这么多?
8. HashMap的主要参数都有哪些?
9. HashMap是怎么处理hash碰撞的?
10. hash的计算规则?

HashMap深入浅出

1.你了解数据结构中的HashMap么?能跟我聊聊他的结构和底层原理么?

HashMap是我们非常常用的数据结构,由数组和链表组合构成的数据结构。数组里面每个地方都存了Key-Value这样的实例,在Java7叫Entry在Java8中叫Node。因为他本身所有的位置都为null,在put插入的时候会根据key的hash去计算一个index值。

2.为啥需要链表,链表又是怎么样子的呢?

我们都知道数组长度是有限的,在有限的长度里面我们使用哈希,哈希本身就存在概率性,两个不同的key,hash有一定的概率会一样,那就形成了链表。每一个节点都会保存自身的hash、key、value、以及下个节点。

3.新的Entry节点在插入链表的时候,是怎么插入的么?

java8之前是头插法,就是说新来的值会取代原有的值,原有的值就顺推到链表中去,因为写这个代码的作者认为后来的值被查找的可能性更大一点,提升查找的效率。但是,在java8之后,都用尾部插入了

4.HashMap的扩容机制

首先我们看下HashMap的扩容机制: 数组容量是有限的,数据多次插入的,到达一定的数量就会进行扩容,也就是resize。

有两个因素: Capacity:HashMap当前长度。 LoadFactor:负载因子,默认值0.75f。

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

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

长度扩大以后,Hash的规则也随之改变。 Hash的公式---> index = HashCode(Key) & (Length - 1) 原来长度(Length)是8你位运算出来的值是2 ,新的长度是16你位运算出来的值明显不一样了。

5.java8之后为啥改为尾部插入呢?

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

扫描二维码关注公众号,回复: 10777334 查看本文章

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

我们可以看到链表的指向A->B->C
Tip:A的下一个指针是指向B的

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

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

B的下一个指针指向了A

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

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

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

6.Java8就可以把HashMap用在多线程中?

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

7.HashMap的默认初始化长度是多少?那为啥用16不用别的呢?

初始化大小是16

因为在使用不是2的幂的数字的时候,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。 只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。 这是为了实现均匀分布。

8.为啥我们重写equals方法的时候需要重写hashCode方法呢?你能用HashMap给我举个例子么?

因为在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都一样,这不是完犊子嘛。

8.你们是怎么处理HashMap在线程安全的场景么?

我们一般都会使用HashTable或者ConcurrentHashMap,但是因为前者的并发度的原因基本上没啥使用场景了,所以存在线程不安全的场景我们都使用的是ConcurrentHashMap。 HashTable我看过他的源码,很简单粗暴,直接在方法上锁,并发度很低,最多同时允许一个线程访问,ConcurrentHashMap就好很多了,1.7和1.8有较大的不同,不过并发度都比前者好太多了。

8.HashMap怎么解决碰撞问题的?

Java中HashMap是利用“拉链法”处理HashCode的碰撞问题。在调用HashMap的put方法或get方法时,都会首先调用hashcode方法,去查找相关的key,当有冲突时,再调用equals方法。hashMap基于hasing原理,我们通过put和get方法存取对象。当我们将键值对传递给put方法时,他调用键对象的hashCode()方法来计算hashCode,然后找到bucket(哈希桶)位置来存储对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当碰撞发生了,对象将会存储在链表的下一个节点中。hashMap在每个链表节点存储键值对对象。当两个不同的键却有相同的hashCode时,他们会存储在同一个bucket位置的链表中。键对象的equals()来找到键值对。

发布了50 篇原创文章 · 获赞 85 · 访问量 161万+

猜你喜欢

转载自blog.csdn.net/qq_24347541/article/details/105488225