我是19年毕业的应届生,工作以后我还一致保持着不停学习的习惯,也时时刻刻关注着学弟学妹和前辈们面试经验的,再结合我自身在数不过来的面试中历练出来的经验,我想告诉大家,知识并不是你了解了就行,面试技巧也很重要,就拿HashMap为例,同样的两个面试者,都对HashMap的底层实现很了解,但是面试结果偏偏是一个月薪五六千,一个年薪20w+,二者的差距究竟在哪里呢? 张三的面试过程(普通人): 面试官:我看你写的对常见的数据结构比较了解,你能讲一下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,我们来看一下这个方法,源码如下: 这样吧,通过一个例子来看这个成环的过程,这个过程十分烧脑,请吃完饭食用!不上示意图估计你就晕了! 假设一个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。程序继续执行到 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;接下来执行 此时整体情况如下图所示: 第三次循环开始,代码有执行到了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到这些知识点呢?为了你们能看懂和理解我是煞费苦心啊,觉得不错的关注,加评论哦😯,期待你的素质三连!
你来面试张三和李四(李四已拿腾讯和阿里offer)
知识点补充
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; } } }
e.next = newTable[i]; newTable[i] = e;
e.next = newTable[i]; newTable[i] = e; e = next;
总结一下
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算