【面试技巧】确认细节 & 边界
中午看到这位同学的面经分享:

之前笔试成绩太差了,一面之后被要求补了一轮伯乐笔试,题目是关于实现本地缓存(需要考虑并发的场景),我第一次碰到这样的题目。之前一直以为笔试都是一些算法题,看到题目瞬间都懵了。写完之后和面试官交流了下自己写的代码,被狠狠批判了,我真的没写过这类的代码,还是太菜了。各位佬有什么思路吗?ps:最后硬着头皮写,使用了concurrentHashMap去实现的,然后借用了一些redis的思想,比如缓存重构的思路,过期entry的惰性删除等。最后的话应该还是自己代码写少了,写的代码依托答辩被狠狠的怼,甚至面试官怼累了,最后他重重的叹了口气【抱歉脸,我真的太菜了】。
让我想起我当年阿里一面前,也做过一个类似的东西。(好像是多线程处理日志文件)
这也算是阿里的特色了,面试前后是有可能让你写一些工程相关的实现的(常见是多线程相关)
我特意翻了下牛客,可以看到 18 号这篇阿里面经里面就有这种非常规算法题,这里提醒大家对目标公司的面经还是要多看,对题型保持高度敏感。
然后回归正题,我们回顾下题目:
题目是关于实现本地缓存(需要考虑并发的场景),我第一次碰到这样的题目。之前一直以为笔试都是一些算法题,看到题目瞬间都懵了。写完之后和面试官交流了下自己写的代码,被狠狠批判了,我真的没写过这类的代码,还是太菜了。
这位同学只说了是本地缓存(需要考虑并发场景),很多细节没说清楚。
那么当我们面试时遇到这种情况,题型不熟悉,有点懵,怎么办?
首先,心态上要稳住,然后大致浏览一下问题,要针对问题去跟面试官沟通一些细节。
比如这里说并发,那是只考虑并发读,还是也会有并发写的情况?(这个会影响你的多线程结构设计,是一写多读,还是多写多读)
本地缓存的内容分布大致是怎样的?(这个可能会影响你的缓存淘汰策略)
......
先跟面试官做完一轮沟通,哪怕最终代码没完全写出来,但这个过程中,你懂得跟面试官确认问题的细节与边界,说明你是有经过思考的,不是只会做题的工具人,这在面试官那是能加一些印象分的。
当然,如果题目已经写得很明确了,你觉得问无可问了,那是另外一回事。(但以我的经验,大多数时候其实都有能挖掘的点)
再针对这位同学的解答:
最后硬着头皮写,使用了concurrentHashMap去实现的,然后借用了一些redis的思想,比如缓存重构的思路,过期entry的惰性删除等。最后的话应该还是自己代码写少了,写的代码依托答辩被狠狠的怼
我觉得用 concurrentHashMap、借用 redis 的思想这些思考方向都是对的。
但是这里可能遗漏了一个核心点,缓存的淘汰策略,如果关注到这点的话,那应该很容易往 lru 之类的方向上凑。
那么最次能写个支持多线程的 lru 。
在此之上,还可以结合一些别的东西,比如你如果跟面试官确认这里缓存内容占用的内存很小,那么就可以考虑用读写分离的双缓冲结构,去降低读写的冲突概率。
这里假定大家有一定的思路了,准备动手写了。
那么第二点,写之前要跟面试官确认写法上的细节。
比如能不能在 IDEA 上写,或者某个 API 忘记了,面试官能不能提醒一下。
只要你的思路清晰,代码结构清晰,那么一些实现细节尽管不到位,但在整体面评上可能也是无伤大雅的。
当然大家还是尽可能精益求精,我这里想表达的是,在面试阶段,你知道这代码自己大概率写不出能跑的了,那也得尽力去挽回面评。
清晰的分析思路、结合具体情况给出的代码结构和实现思路,在这些东西都有了之后,实现此时只是其中一小环。
我那会阿里一面的多线程日志程序也是写不出能跑的,因为我当时都好久没写 JAVA 了,在腾讯实习写 Django + Vue,实习期间复习又都在搞 C++,当时对 JAVA API 的记忆仅能支持我写算法题。
但是我通过做题前和面试官的互动分析,和代码上注释思路,虽然写出来的代码不能跑,但还是通过了笔试,拿到了面试机会。
(有些 API 我都不记得了,只能写个似是而非的上去,然后备注下,能跑就见鬼了。)
有些同学看到这觉得自己明白了,但其实未必真的明白。面试的时候一遇到难题,很多人会直接头脑空白,之前听过看过的方法一下子都忘光了。
我这里提供一个训练方法:
1. 大家平时看面经的时候,看到一些不太懂的问题,可以主动思考下,这个问题我可以跟面试官确认哪些细节?
2. 然后记录下来,等你后续掌握这块内容之后,你再回过头来看你之前记录的细节询问方向,看有没有问题。
大家也可以自行迭代训练方法。
仅供参考。
我是菜饼,我们下期见。
------------------------------
补充:我看鱼与玉与雨同学已经把题目链接补充在讨论区了:题目链接
因为一开始给的信息比较少,我就只能推测面试官意图了,但看完题目,其实考察的内容并不复杂。
题目给出了几个点:
1.给出了读写缓存的函数定义。
星球没法复制,我就简单手打下了
可以看到这个函数定义其实给出了比较明确的指向性:
- 有过期时间,那么隐含了在过期时间内,不会被淘汰的细节。(也就是可以不考虑内存问题,当然这个点是可以跟面试官确认清楚的)
- 其次,给出了 key-value 分别是一个 userId 和 一个 User
- 有过期时间,过期时间到达后,应该进行淘汰
这些信息都不难获取,接下来继续看:
- 并发场景,主要考虑的是降低DB的压力,也就是说,需要尽可能提升读的速度,减少请求 DB 的情况(这也是我们做缓存的意义所在,很合理)
- 那么很容易想到用 ConcurrentHashMap 来存储,毕竟 map 的 get 的平均时间复杂是 O(1),符合我们对读速度的要求,数据结构上也吻合
接下来,(跟面试官互动确认细节那些我这里就不多说,补充这里主要是考虑怎么解题)
先思考最简单的方法:
key: userId
value: {user, expireTime} // 新搞个结构把 user 包起来,加个过期时间,或者用 pair 之类的东西
那么接下来考虑这样能否实现,会有什么问题?
1.显然,新增、更新、读取都是在 concurrentHashMap 上操作,似乎没啥问题;
2.考虑如何删除过期数据
3.读取不到的时候要去 DB 更新,这里需要加锁,避免多个线程同时读 DB,对 DB 造成影响
想了想直接上手写了。
我简单写了一版,好多年没写 JAVA 了,估计会有些问题,大家做个参考就好。
https://paste.ubuntu.com/p/9NfJ8Zjnsh/
删除过期缓存的来不及写了,可以自己想想吧,难度不大。
诶,我看这同学连个赞都不给我点,我也不知道我在这 blabla 写图啥。
希望能有所帮助吧,11点多了,收工下班,就酱。
#面试技巧# #求职#
