您的位置:首页 > Web前端

HashMap面试时你和年薪20万的回答差在了哪里?已拿腾讯和阿里offer面经干货!

2020-04-19 20:02 459 查看

我是19年毕业的应届生,工作以后我还一致保持着不停学习的习惯,也时时刻刻关注着学弟学妹和前辈们面试经验的分享,再结合我自身在数不过来的面试中历练出来的经验,我想告诉大家,知识并不是你了解了就行,面试技巧也很重要,就拿HashMap为例,同样的两个面试者,都对HashMap的底层实现很了解,但是面试结果偏偏是一个月薪五六千,一个年薪20w+,二者的差距究竟在哪里呢?

你来面试张三和李四(李四已拿腾讯和阿里offer)

张三的面试过程(普通人):

面试官:我看你写的对常见的数据结构比较了解,你能讲一下HashMap是个什么样的数据结构吗?

张三:OK,数组+链表+红黑树。(心里想这也太简单了)

面试官:你说的对,回去等通知吧。

张三:....

李四的面试过程(神仙的堪称教科书式的回答)

面试官:我看你写的对常见的数据结构比较了解,你能讲一下HashMap是个什么样的数据结构吗?

李四:可以的,HashMap在JDK1.7到1.8做了一个比较大的改变:1.7的时候HashMap使用的是Entry数组作为表格+表格内的链表。1.8的时候HashMap使用的是Node数组作为表格+表格内部的链表/红黑树。如果链表中的元素超过8个,使用红黑树。

李四:为什么会有这个改变呢?是因为在JDK1.7的时候hashMap使用的是头插法,虽然hashMap并不是线程安全的容器,但是在并发情况下,使用hashMap会带来一个问题,如果插入元素的两个线程都调用了扩容方法,里面有一个resize方法,这个方法又会调用里面一个transfer方法,把里面的一些Entry进行rehash,会导致链表成环的问题,线程在Get的时候出现一个死循环的情况。然后还有可能在使用的时候没有加锁,hashMap并发的情况下不能保证数据是安全和正确的,可能我put进去的一个值取出来还是我put进去的那个值。JDK1.8的时候,它变成了链表+数据+红黑树的这样一个结构,把原来的Entry变成了一个Node节点。它的整个put的过程做了一个优化,emmm....这个过程需要我说一下吗?

面试官:emmmmm....你先简单说一下它的扩容机制吧,因为刚刚你提到了它的扩容机制。

李四:扩容机制...就是我们在初始化一个HashMap的时候,我们如果没有初始化它的capacity,它的默认初始化长度就是16,扩展因子是0.75,他会计算出来一个threshold做为它的一个阈值,首次扩展阈值为12。如果当我在put的时候,他会判断我的这个size是否大于当前的这个阈值,如果大于,就会扩张为之前的一倍,就是将原来的Entry进行一个resize的过程。

面试官:好...OK...

面试官:刚刚你有提到1.7的时候是头插法,1.8的时候变成了尾插法,头插法的时候会有死循环,这是线程不安全的原因之一,那么1.8之后它就是线程安全的了吗?

李四:它也不是一个线程安全的,尾插法相比头插法没有改变它数据插入的这样的一个顺序,所以不会出现成环的情况。

面试官:好...OK...

面试官:那它线程不安全,你在日常的开发中,去怎么保证它的线程安全呢?

李四:我一般会使用ConcurrentHashMap这种线程安全的集合容器。

面试官:那线程安全的还有HashTable啊,或者我给它加一个Synchronsized或者Lock还有Collection.synchronsized,这些方法都能保证它的一个线程同步,你为什么选择了ConcurrentHashMap?

李四:第一就是ConcurrentHashMap的并发度是更高的,我们知道普通的HashTable是直接对里面的方法进行了一个Synchronsized就是加了一个对象锁,但是ConcurrentHashMap在JDK1.8之后变成了同样的数据+链表+红黑树这样的一个结构,它只会锁住我目前获取到的那个Entry所在的节点的那个值,上锁的时候它采用的是CAS+Synchronsized,再加上在JDK1.6以后对Synchronized进行了优化,引入了锁升级的过程,所以ConcurrentHashMap的效率也是更高的。

面试官:oh...可以...

面试官:刚刚你提到了锁升级,你简单给我介绍一下锁升级的过程吧。

李四:我们new一个普通对象,首先上的是偏向锁,获取到锁资源的这个线程再来的时候会优先让它拿到锁,如果没有拿到或者有轻度竞争,升级成轻量级锁(自旋,自适应自旋),也就是一个CAS锁,也叫乐观锁,就是一个比较与交换的过程,CAS如果没有设置成功的话,会进行一个自旋,如果竞争太激烈,自旋到一定的次数,就升级成重量级锁。这样就保证了它的性能的问题。

题外话:我想李四一定看了这篇博客【Synchronsized你以为你很懂】

面试官:好...OK...其实你还漏了一个无锁的状态,就是刚new的对象,回去判断一下。不过没关系,你把核心的东西都答出来了。

上面就是两个人的面试过程,我想今天你是面试官,你应该心里也有数了吧,张三和李四可能他俩会的东西都一样,你不能说张三就是不知道李四哒的那些点,面试官如果一个点一个点的问,张三可能也会打出来,但是实际面试中,不仅仅考察的是你的知识点,更重要的是你有没有真正的理解,对一个知识形成一个知识树。有一句话:牵一发而动全身。

下面我们就来看一下李四的知识树,做为普通人这也是你需要学习的,如何把一个知识融会贯通,学会串蚂蚱:

李四在每一个节点上都问了自己一遍为什么?这样面试官在问到自己的时候就能对答入流。

知识点补充 

关于头插法的链表成环问题,这是一个老生长谈的问题了,很多人理解不了,今天我用我的办法给你理清这个过程。

问题正如李四回答出在了transfer,我们来看一下这个方法,源码如下:

[code]void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}

int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}

这样吧,通过一个例子来看这个成环的过程,这个过程十分烧脑,请吃完饭食用!不上示意图估计你就晕了!

假设一个HashMap已经到了resize的临界状态,此时两个线程1和线程2,在同一时刻对这个HashMap进行put操作,如图一和图二,此时达到了resize的条件,两个线程各自进行resize的第一步,也就是扩容:

图一: 图二:

假设两个线都走到了rehash的步骤,也就是上面的方法transfer,假设此时线程2遍历到c对象,刚执行完 Entry<K,V> next = e.next;线程被挂起。对于线程2来说,此时e = Entry-c;next = Entry-b;

这时候线程1进行rehash,当rehash完成后,结果如下(图中的e和next代表线程2的引用)

到这里还没有什么问题,接下来线程2恢复,继续执行它的rehash,当执行到  int i = indexFor(e.hash, newCapacity); 这一行时,i == 3,因为你观察上面的图线程1对于Entry-c的hash结果也是3。程序继续执行到

[code]e.next = newTable[i];
newTable[i] = e;

Entry-c节点放到了线程2的数组下标为3的位置,并且e指向Entry-b。此时e和next的指向是e = Entry-b;next = Entry-b。

接着是新一轮的循环,又执行到Entry<K,V> next = e.next; 此时的e =Entry-b;next = Entry-c;接下来执行

[code] e.next = newTable[i];
newTable[i] = e;
e = next;

此时整体情况如下图所示:

第三次循环开始,代码有执行到了Entry<K,V> next = e.next;此时e = Entry-c; next = Entry-c.next = null;

最后当我们执行 e.next = newTable[i];的时候,奇迹发生了!

newTable[i] = Entry-b

e = Entry-c

Entry-b.next = Entry-c

Entry-c.next = Entry-b

链表出现了环形!整体的情况如下图所示:

但是要注意的时候,你不执行任何操作其实是看不出这个问题的,问题此时没有直接产生,当调用Get查找一个不存在的Key,这个Key的hash的结果刚好等于3的时候,由于存在环形结构,程序就会进入死循环。

总结一下

  • 知识形成体系,形成一个知识树。可以通过经常画脑图,多问自己为什么锻炼这个能力。
  • 面试的时候并不是面试官问什么答什么,面试官问到一个知识点,你可以把你知道的与之相关的点自己说出来,这样能体现你愿意去探索和研究,还能表现给面试官你知识面的宽度,就像李四一步步引导着面试官去问到自己擅长的领域和问题。

我这样给你讲,你是否能Get到这些知识点呢?为了你们能看懂和理解我是煞费苦心啊,觉得不错的关注,点赞加评论哦😯,期待你的素质三连!

小码Blog 原创文章 77获赞 929访问量 23万+ 关注 私信
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: