【面试技巧】确认细节 & 边界

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

下载 APP