今天的练习
节后回来上班,三轮都答完了。Java 那轮问的是以前没碰过的 Lambda、函数式接口和 Stream,前端和数据库网络那两轮十二个小问全过。数据库和网络里有三个是新的:慢 SQL 怎么查、两个事务抢同一行会发生什么,Java 那轮整个板块都是新的。
题目一:Lambda 到底在替谁干活
以前想给方法传一段逻辑,比如排序的比较规则,只能写个匿名内部类:new Comparator<String>() { public int compare(String a, String b) { return a.length() - b.length(); } }。用 Lambda 把这段改成一行怎么写?Lambda 本质上是在替谁干活?它凭什么能被塞进方法的参数里?
我写的是 (a, b) -> a.length() - b.length()。概念那问我答的是它在替函数式接口的匿名内部类干活,因为目标类型就是个函数式接口,Comparator<String> 只有一个抽象方法 compare,Lambda 正好把这个方法实现掉。
助手说概念没错,但实现机制得说清。编译器不会真给你生成一个匿名内部类的 class 文件,字节码里走的是 invokedynamic 指令,运行时由 LambdaMetafactory 动态生成一个实现类。这个区别能看出面试官是不是真读过字节码。
后面两问也答住了。只有「只有一个抽象方法」的接口才能用 Lambda 接,这种接口叫函数式接口;@FunctionalInterface 是写给编译器看的,让它帮你检查这个接口满不满足条件,运行时不加没有任何区别。JDK 自带的一抓一大把,Runnable、Comparator、Consumer 都是。助手补了个常被追问的细节:所谓「一个抽象方法」不算 Object 里那些 public 方法,你要是把 boolean equals(Object) 再声明一遍,它不算第二个抽象方法。
Stream 那半题我答的是「对集合数据做链式、声明式处理的工具」,跟 for 循环的差别在于遍历和取值都是自己在控制,而 Stream 只声明先过滤、再映射、再收集,怎么遍历由它内部处理。那行代码里的三个操作我也逐个说了:filter 保留年龄大于 18 的,map 把每个 User 转成它的 name,collect 把流水线的结果收成一个 List。助手补了三条硬性质:Stream 不存数据、一条流通常只消费一次、中间操作是惰性的,不遇到 collect 这种终止操作根本不会跑。他说这三条比「声明式」更实在,被问「和 for 循环的区别」时可以直接甩出来。
题目二:一条慢 SQL 怎么查
有条命令能让 MySQL 把它打算怎么执行这条 SQL 列出来:走不走索引、走哪个索引、大概扫多少行。这条命令叫什么?输出里你会先看哪几列?type 那一列写着 ALL 是什么意思?key 那一列是 NULL 呢?
我答的是 EXPLAIN,先看 type、key、rows、Extra,还有 possible_keys。ALL 是全表扫描,key 是 NULL 就是没走索引。
助手补了三处。type 这一列的值有轻重之分,const、eq_ref、ref、range、index、ALL,越往右越糟。看见 index 和 ALL,基本说明这条 SQL 有问题。rows 是预估值,不是精确扫了多少行,它的作用是判断量级。最该盯的是 Extra。出现 Using filesort 或者 Using temporary,说明排序和临时表没走好;反过来出现 Using index 是好事,说明用上了覆盖索引。
题目三:两个事务抢同一行
一条 update 在改库存,这时候另一个事务也要改同一行。后到的那个能直接改吗?不能的话它在等什么,MySQL 靠什么让它们排队?锁分读锁和写锁,加了读锁,别人还能不能读、能不能写?加了写锁呢?
我答的是不能改,先到的那个事务拿了排他锁,第二个得等它提交才能继续,靠 InnoDB 排队。读锁别人能读不能写,写锁别人读不了也写不了。
助手补了两个会追问的点。等是有上限的。超过 innodb_lock_wait_timeout(默认 50 秒)还没等到,会报 Lock wait timeout exceeded。这条报错在生产里不罕见,多半意味着有事务忘了提交,或者事务开得太长。另外 InnoDB 还有死锁检测,发现几个事务绕成了环,会挑一个回滚掉,另一个放行。
题目四:HTTPS 里两种加密各管哪一段
对称加密和非对称加密在 HTTPS 里都用上了,各自用在哪一步?为什么不干脆全程都用非对称加密?证书解决的是什么问题?浏览器拿到服务器给的公钥,凭什么就信它?
这题接着上个月答过的往下走一步。我答的是:非对称加密用在 TLS 握手阶段,对称加密用在握手完成后的数据传输阶段。全程都用非对称不行,它计算慢、开销大,不适合拿来加密大量数据。证书这块,浏览器会信是因为这个证书由受信任的 CA 签名,浏览器内置了这些 CA 的根证书。这防的是中间人伪造公钥、冒充服务器。
助手说主干没问题,可以再补一层细节。实际验证的是一条链:服务器的证书由中间 CA 签,中间 CA 由根 CA 签,根证书预装在系统或者浏览器里。除了顺着链验签名,还要校域名对不对得上、有没有过期、有没有被吊销。这三样里任何一样不对,浏览器都会拦下来报证书错误。
题目五:前端那轮的三道
事件循环那题给了一段四行代码,让我说打印顺序。答案是 1、4、3、2,我答对了,也答出来这两类任务分别叫微任务和宏任务。助手补了机制那层:同步代码全部跑完之后,先把微任务队列清空,才去取一个宏任务,每个宏任务执行完还会再清一次微任务队列。
这题里还夹了一个复测。上个月底,我把一段闭包代码打出三个 3 的原因归到了「setTimeout 是宏任务」上。今天回头看,我自己改口了:那跟执行时机无关,是作用域的问题,var 只有一个变量被三个回调共享。助手说改对了,但题目后半问我漏了,直接在循环里同步打印,出来的是 0、1、2。归因改对了,举证这一步也得跟上,面试官常常追一句「那你怎么证明」。
存储那题问了 localStorage、sessionStorage、cookie 的存活时间和容量,以及 token 存哪。三个的差别我说清楚了,cookie 多的那个特性是会随请求发给服务器。token 那问助手说我的判断算成熟。首选 HttpOnly 加 Secure 加 SameSite 的 cookie;只能在前端三选一的话,选 sessionStorage,别用 localStorage。他补了一点我没想到的。HttpOnly 防的是脚本偷读,可挡不住 CSRF,所以 SameSite 或者 CSRF token 得配上一个。sessionStorage 也躲不过 XSS,区别只在窗口期和持久化。更严的做法是 refresh token 放 HttpOnly cookie,access token 只留内存变量里,根本不落存储。
还有一题是 keep-alive,新考点,一次就过了。它是 Vue 的内置抽象组件,用来缓存组件实例,切走时不销毁、切回来复用,常跟动态组件或者 router-view 配。被缓存的组件会多出 activated 和 deactivated 两个钩子。助手补了三个 props:include、exclude、max,max 按 LRU 限制缓存个数。还有一个坑。被缓存的组件切走时不触发 unmounted,清理定时器和事件监听的逻辑得挪到 deactivated 里,不然会一直挂在后台。
节后开工就撞上三个全新板块,状态比我自己预期的好。