快手后端 Java 的面经,我花了两天时间整理完,发现能写的内容比想象中多。整个流程跑下来,最大的感受是:快手的面试官不太跟你玩虚的,每个问题都会顺着你的回答一直追到底,八股背得再熟,不理解底层原理照样卡壳。这篇面经主要面向准备大厂后端岗位的 Java 工程师,不管是校招还是社招,考察主线都绕不开 Java 基础、并发、JVM、Spring、MySQL、Redis、分布式和算法这几大块,只是侧重点和深度不同。
我自己是社招背景,前后端分离项目做了不少,也踩过线上 OOM、消息积压、缓存穿透这些坑,所以聊起来还算有东西。但如果你正准备面试,光有项目经历不够,很多基础题需要能脱口而出,而且要经得起连环追问。
1. 快手后端 Java 面试的流程与整体风格
1.1 三轮技术面怎么分工的
快手的面试流程一般是 3 到 4 轮技术面加 1 轮 HR 面,我走的社招是三轮技术面加一轮交叉面。第一轮通常由组里的主力开发来面,问得最细,Java 基础、并发、集合源码、JVM、MySQL,逮到什么问什么,代码题也会在这一轮出现。第二轮一般是你未来直属领导来面,除了继续考察技术深度,还会重点问项目、问架构设计、问线上问题处理经验。第三轮有的部门是总监面,有的部门是交叉面,这轮偏重整体技术视野,比如让你设计一个系统、聊聊技术选型的原因、聊业务理解,也会考察你的沟通表达和思考方式。
很多人容易忽略的是:快手每一轮面试都有独立的评价标准,不会因为你上一轮面得好就放松要求。我第一轮聊得很顺,结果第二轮从项目里的一个小细节切入,一路追问到分布式事务,差点没接住。所以不要抱着“这轮简单”的心态去准备,每一轮都要当终面来打。
1.2 快手面试官的提问习惯
面完下来我发现快手面试官有两个明显特点。第一个是喜欢连环追问,特别典型。比如你先说出“HashMap 不是线程安全的”,他马上会接一句“那你再说说 ConcurrentHashMap 是怎么解决线程安全问题的”,你说完“CAS 加 synchronized 锁桶”,他又问“为什么不直接用 synchronized 锁整个数组”,然后一路追到扩容时怎么保证线程安全。这种追法对只背结论的人非常不友好,但对真正写过源码、思考过设计取舍的人反而简单。
第二个特点是场景题特别多,而且会结合快手自己的业务。比如短视频信息流、直播弹幕、评论盖楼、秒杀活动这些场景,面试官让你当场设计一套方案。我印象最深的是在被问到 Redis 缓存时,面试官直接说“假设快手某个视频突然爆了,大量用户同时刷评论,你会怎么做”。这种题没有标准答案,考察的是你有没有经历过类似场景,以及能不能把技术方案讲得自洽。
提示:准备快手面试,别把精力全押在背题上。把每个常见问题当成“为什么”来准备,多问自己几层原因,比你背三十套题都管用。
2. Java 八股文的真正考点:基础与并发
2.1 HashMap 到 ConcurrentHashMap:一条线追到底
Java 基础这块,HashMap 几乎必考,但考法不是让你背结构,而是看你有没有理解设计。我记得第一轮面试官给的题目是:“HashMap 在 JDK 7 和 JDK 8 里有什么变化?”这个问题看起来基础,其实可以展开很多。我说了“数组加链表”变成“数组加链表加红黑树”,链表插入从头插变成尾插,然后他紧接着就问了三个问题:
第一,为什么链表长度到 8 才转红黑树?这里要说清楚泊松分布的概率,更要说明红黑树虽然查询是 O(log n),但节点占用空间比链表大,而且插入和删除有旋转成本,所以不能链长一超过 2 就转。第二,为什么扩容后 JDK 8 不用重新算 hash?因为容量是 2 的幂,扩容后元素的新位置要么在原下标,要么在原下标加旧容量,这个可以通过hash & oldCap是否为 0 来判断。第三,JDK 7 的头插法在并发扩容时为什么会形成环形链表?这个其实就是因为头插法会把链表顺序反转,并发时两个线程同时操作同一个桶,某个节点的 next 指针会被错误覆盖,形成循环引用。
说到 ConcurrentHashMap 的时候,我直接说它是“CAS 加 synchronized 锁桶”,面试官就追问“锁的粒度是什么”,这就得说清楚:JDK 8 里锁的是每个桶的头节点,也就是Node对象,而不是整个数组。他还问“为什么不用HashTable那种直接锁整个表的方式”,这个就比较容易回答,锁粒度太大,并发度太低,吞吐量上不去。
2.2 synchronized、volatile 与锁升级
并发部分几乎必问synchronized和volatile,但问的层次不一样。基础题是“volatile 的两个语义是什么”,这个会答“可见性和禁止指令重排”就行。但快手面试官会继续问:“volatile 能不能保证原子性?为什么不能?”你要知道i++这种操作是读、改、写三步,volatile 只能保证每一步之间对其他线程可见,但三步之间别的线程可能已经改了值,所以不具备原子性,要原子操作得用AtomicInteger或者锁。
synchronized的锁升级过程也是高概率出现的题。我面试时就被问到了:“synchronized 在 JDK 6 之后有什么优化?”这个就要讲到偏向锁、轻量级锁、重量级锁的升级路径。重点要说出触发条件:偏向锁是同一个线程反复进入同步块时,通过 CAS 把 Mark Word 里的线程 ID 改成自己;如果有其他线程竞争,就升级成轻量级锁,用自旋的方式抢锁;自旋超过一定阈值或 CPU 核心数不允许,就膨胀成重量级锁,靠操作系统互斥量阻塞。还有一个容易被追问的点,就是“偏向锁为什么在 JDK 15 里被废弃了”。主要原因就是现代应用里线程竞争远比以前激烈,偏向锁带来的收益已经盖不住它的维护成本和暂停开销。
对比synchronized和ReentrantLock也要会讲。除了“可中断、可超时、可公平”这些区别,最好能补一句“synchronized 是 JVM 层面的 monitor lock,而 ReentrantLock 是基于 AQS 的”,显得你理解更深入。我那次提了一嘴 AQS,面试官马上抓住问“AQS 的 state 是干什么的”。这里要说清楚:state 是同步状态,在 ReentrantLock 里表示锁被重入的次数;在 Semaphore 里表示剩余许可证数量;在 CountDownLatch 里表示未完成的计数。不同同步器复用同一套队列框架,区别就在 state 的语义和 tryAcquire/tryRelease 的实现上。
2.3 线程池的四个拒绝策略
线程池也是快手面试的常客,而且喜欢跟业务场景结合。核心问题逃不开:核心线程数怎么设置、任务队列怎么选、拒绝策略用哪个。我当时的回答是:
- CPU 密集型任务,线程数设置为
CPU 核数 + 1左右 - IO 密集型任务,线程数可以设大一些,常见经验是
CPU 核数 * 2或者可以根据公式CPU 核数 / (1 - 阻塞系数)计算 - 队列一般选有界队列,避免任务无限堆积打满内存
拒绝策略这块,AbortPolicy抛异常、CallerRunsPolicy调用者执行、DiscardPolicy静默丢弃、DiscardOldestPolicy丢弃最老任务。面试官问“生产环境你用哪个”,我建议如果业务允许,优先考虑CallerRunsPolicy,至少不会丢任务,还能起到天然限流作用。如果要求更高的可靠性,可以自定义拒绝策略,把任务持久化到 MQ 或者数据库,等系统恢复后再补处理。
提示:线程池几个核心参数最好能背出
ThreadPoolExecutor构造方法里每个参数的含义和顺序,因为有些面试官会冷不丁让你写一行创建线程池的代码,参数顺序写错就很尴尬。
2.4 一道并发代码题
第一轮面试最后,面试官给了段代码题,类似下面这种:
public class Counter { private int count = 0; public void increment() { count++; } public int getCount() { return count; } }问题很简单:多个线程同时调用increment,最终结果比预期小,为什么?怎么改?
这个题本身不难,但面试官想看你会不会往外延伸。我的回答分三层:第一层,count++不是原子操作,读、写之间存在竞争窗口,所以结果丢失更新;第二层,最简单的改法是加synchronized或用AtomicInteger,但这两种方式的语义不同,synchronized锁的是代码块,AtomicInteger用的是 CAS 乐观锁,适合竞争不激烈的场景;第三层,如果并发量大,还要考虑用LongAdder,它把热点拆成多个 cell,更新时分散到不同 cell 上,最后求和,适合写多读少的统计场景。这层延伸是我复盘时觉得最加分的地方,因为面试官确实追问了“AtomicInteger 在超高并发下有什么问题”。
3. JVM 与线上故障排查:OOM 也能聊出干货
3.1 JVM 内存与 GC 的高频知识点
JVM 这里快手的考察重点很明确:内存区域划分、垃圾回收算法和收集器、类加载机制,但每个问题都要求能联系到线上问题。比如“内存区域”听起来基础,但面试官会考OutOfMemoryError有哪些类型,每种对应哪块区域。堆内存不足会抛java.lang.OutOfMemoryError: Java heap space;元空间不足会抛java.lang.OutOfMemoryError: Metaspace;栈深度超限是StackOverflowError;还有直接内存不足,会抛OutOfMemoryError: Direct buffer memory。这些异常类型如果不熟悉,面试时很容易被问懵。
垃圾回收部分,面试官问我“CMS 和 G1 有什么区别”,我的回答框架是这样的:CMS 关注低停顿,目标是减少 GC 停顿时间,使用标记清除算法,会产生内存碎片,所以 CMS 老年代有一个-XX:CMSFullGCsBeforeCompaction参数来控制碎片整理。G1 把堆划分成一个个 Region,通过维护一个优先列表来回收收益最大的 Region,同时 G1 的停顿时间是可以预测的,可以用-XX:MaxGCPauseMillis设置目标停顿时间。如果面试官继续追问“那 JDK 11 之后的 ZGC 呢”,你能说出“ZGC 是着色指针加读屏障,停顿时间基本不随堆大小增长”就已经超过大部分人了。
3.2 类加载与双亲委派
类加载机制基本必考,但光背“双亲委派”四个字没用,一定要能说清它解决什么问题。我当时说“为了避免类被重复加载”,面试官纠正我说这不准确,然后补了一句“更核心的是保证核心类库的安全性,防止用户自定义的 java.lang.String 替换掉 JDK 自带的”。这个点我记忆很深刻,所以建议你也把“沙箱安全”和“避免重复加载”这两个目的都讲出来。
另外常见的追问是:“如果你自己写了一个和java.lang.String同包同名的类,能不能加载?”答案是不能,因为双亲委派会把请求委托给 Bootstrap ClassLoader,而它发现已经加载过java.lang.String,就会直接返回已加载的类,不会加载你写的那个。还有一个比较偏的点:SPI 机制里为什么要有线程上下文类加载器,因为双亲委派在ClassLoader加载 JDBC 驱动这种场景下会失效,父加载器需要反向委托给子加载器,线程上下文类加载器就是干这个的。
3.3 一次 OOM 排查现场复盘
我面试时被问到“你线上遇到过 OOM 吗”,这个我太有发言权了。之前做一个数据导出功能,运行一段时间后接口超时,紧接着服务直接挂掉,日志里打出了java.lang.OutOfMemoryError: Java heap space。我当时用jmap -heap pid看了堆参数,-Xmx4g堆却几乎被占满,jstat -gcutil pid 1000看到老年代占比一直在 95% 以上,Full GC 次数频繁但回收效果很差。
我顺着这个思路继续排查。先jmap -dump:format=b,file=/tmp/heap.hprof pid导出了堆转储文件,然后用 MAT 分析,发现占内存最大的对象是一个ArrayList,里面全是导出任务的数据对象,每个对象里还挂着一个大字符串,存的是导出的 CSV 行内容。产生的原因是我们把所有导出数据都攒在内存里,等全部生成完才写文件,数据量一大就直接栈堆了。修复方案是把“全量放内存后一起写”改成“流式写文件,边生成边写”,同时在接口层增加了并发数和数据量上限的控制。
这个案例在面试里是很加分的素材,因为它完全符合“发现问题、定位问题、解决问题、预防问题”的闭环。我建议你也在面试前整理一个自己真实的线上故障案例,比背一百个 JVM 参数都有用。还有一个小技巧:提到排查工具时自然带出jstat、jmap、jstack、MAT、Arthas,面试官会觉得你是真的干过活,而不是只看过书。
提示:如果被问到“怎么判断是内存泄漏还是内存不足”,可以从 GC 日志和监控曲线判断——内存使用量只升不降,并且 Full GC 越来越频繁,基本就是泄漏;如果是某个大请求突然占满堆,一般是峰值压力问题。
4. Spring 与微服务:项目深挖的功力所在
4.1 Spring IoC、AOP 与自动装配
Spring 是后端的立身之本,快手不会只问“IoC 是什么”这种题,而是会把问题落在原理和实现上。我在二面时被问到:“Spring 的 Bean 生命周期你都熟悉哪些阶段?”这个题说难不难,说简单也不简单,需要把BeanDefinition的解析、实例化、属性填充、初始化前中后、销毁等阶段捋清楚。完整回答可以这么走:先解析配置类或 XML 生成BeanDefinition,然后通过反射创建实例,接着依赖注入,再执行BeanNameAware、BeanFactoryAware、ApplicationContextAware这些 Aware 回调,然后是BeanPostProcessor的 postProcessBeforeInitialization,接着是@PostConstruct和InitializingBean.afterPropertiesSet,再走 postProcessAfterInitialization,最后是 AOP 代理的生成。这个过程能顺下来,说明你确实理解 Spring 容器的核心流程。
AOP 的高频追问是“代理对象什么时候创建”。常见误区是认为 Bean 创建时直接生成代理,但实际上是AbstractAutoProxyCreator在 Bean 初始化后,通过BeanPostProcessor机制判断当前 Bean 是否需要被切面增强,如果需要就创建代理对象并返回。Spring Boot 的自动装配也是必考,重点要讲清@SpringBootApplication是@Configuration、@EnableAutoConfiguration、@ComponentScan的组合,而@EnableAutoConfiguration会通过AutoConfigurationImportSelector加载META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports里的配置类,但真正生效还要看@ConditionalOnClass、@ConditionalOnProperty这些条件注解是否满足。
4.2 事务传播与失效场景
Spring 事务这块,面试官常常会拿“事务失效”来考察实战经验。常见失效场景要能脱口而出几个:@Transactional加在非 public 方法上、方法内部自调用导致事务切面没有生效、异常被 catch 掉没抛出去、抛出的是受检异常但没指定 rollbackFor、数据库引擎不支持事务。这些如果只说“方法内部调用会失效”是不够的,最好解释一句“因为 Spring 事务是基于动态代理实现的,自调用时调用的是原始对象的方法,而不是代理对象的方法,所以事务拦截器没有执行”。
有个细节我特别想强调:REQUIRED这种传播机制其实也是高频考点。面试官会问你“两个加了@Transactional的方法互相调用,事务是怎么传播的”。要分情况讲:如果 A 方法加@Transactional调用了 B 方法,B 也有@Transactional,默认传播行为是REQUIRED,B 会加入 A 的事务,两个方法在同一个事务里;如果 B 的传播行为是REQUIRES_NEW,B 会挂起 A 的事务,自己新开一个事务,B 提交失败不影响 A 已经提交的部分,但如果 B 是内层且 A 之后抛异常,B 已经提交的内容不会回滚。
4.3 微服务组件与治理
快手后端整体是微服务架构,所以微服务组件也会被问到。这边的考察方式比较实际,不会问你“Nacos 和 Eureka 有什么区别”这种背题型的,而是场景化的。比如:服务启动时如何优雅上线?服务发版时如何优雅下线?这背后涉及注册中心、负载均衡、健康检查等多个环节。我提到我们项目里用了 Nacos 加 OpenFeign,面试官就问“OpenFeign 的调用过程是怎样的”。这题需要答出:接口方法通过FeignClient生成动态代理,把方法注解解析成 HTTP 请求,底层通过LoadBalancer选择服务实例,再通过ConnectionTarget执行请求,最后解码响应。光答“远程调用”四个字是不够的。
分布式链路追踪也是加分项,我们项目用了 SkyWalking,所以面试时我顺便讲到了 TraceId 怎么透传、跨服务时如何通过 header 传递上下文。这些项目里的真实细节容易让面试官对你产生兴趣,也能自然引出后续的分布式话题。
5. MySQL 与 Redis:数据层的硬核考察
5.1 MySQL 索引与事务隔离
MySQL 是后端面试的必考数据层,快手的题主要集中在索引、事务、锁和慢 SQL。索引这边,B+ 树的追问频率极高,面试官会问“为什么用 B+ 树不用 B 树或者红黑树”。回答的核心是:B+ 树的数据都在叶子节点,非叶子节点只存索引和指针,所以树更矮、IO 次数更少;叶子节点之间有链表串联,适合范围查询和排序;而红黑树是二叉树,数据量大时树高太大,磁盘 IO 次数多,不适合存储引擎这种场景。
事务隔离级别和 MVCC 也是必备题。面试官问我“RR 级别下,MVCC 是怎么解决幻读的”,这个比较有深度。简单版本是:MVCC 通过版本链和 ReadView 实现快照读,让普通查询看不到其他事务未提交的数据。但快照读解决不了当前读的幻读,真正在 RR 级别下解决当前读幻读的手段是间隙锁gap lock,它锁住的是记录之间的间隙,让其他事务无法在间隙里插入新数据。能答到这一层,面试官一般会满意。
5.2 慢 SQL 排查与主从复制
“线上有个接口突然变慢,你怎么排查”也是高频题。我会先说排查链路:先看是不是整体系统问题,再看 SQL 有没有变化,然后show processlist查看是否有锁等待,再用explain分析执行计划,看type是不是ALL全表扫描、key有没有走索引、rows估算的扫描行数是不是很大。面试官通常会追问“索引没走可能是什么原因”,这就要说:查询条件字段上有函数操作导致索引失效、隐式类型转换导致失效、like '%xx'前置通配符、or连接非索引字段、统计信息过期导致优化器选错执行计划等。
主从复制的问题我这次也遇到了:“主从延迟怎么处理?”我当时给了几个思路:针对读多写少的场景可以把某些强一致读的请求强制走主库;用半同步复制保证主库提交后至少一个从库收到 binlog;如果延迟严重,可以考虑在业务层用缓存暂存刚写入的数据。这类题没有标准答案,但你的方案要能自圆其说。
5.3 Redis 缓存穿透、击穿、雪崩与分布式锁
Redis 这块高频中的高频。缓存穿透、击穿、雪崩一定要分开说清楚:穿透是查一个不存在的 key,缓存和数据库都没有,请求直接打到数据库;解决方法是把空值也缓存,或者用布隆过滤器在最前面拦掉。击穿是某个热点 key 过期的一瞬间大量请求打到数据库;解决方法是互斥重建、逻辑过期或者热点数据不过期。雪崩是大量 key 同时过期,或者 Redis 实例宕机导致请求全部打到数据库;解决方法是过期时间加随机值、多级缓存、限流降级、集群高可用。
分布式锁也是一个必考场景题。我当时被问到“用 Redis 怎么实现分布式锁”,我不建议只回答setnx,最好能给出完整的生产级方案:加锁用SET lock_key request_id NX PX 30000,这里要带 request_id 是为了防止释放锁时把别人的锁解开,要用 Lua 脚本保证“判断是同一个锁 + 删除”的原子性;锁要设置过期时间避免死锁,但过期时间又可能因为业务没执行完而提前释放,所以要用续期机制,也就是 Redisson 的看门狗,默认每 10 秒续期一次,锁的有效期是 30 秒。如果你能把这段完整讲出来,面试官大概率不会再往下难为你。
6. 分布式与高并发场景设计:拉开差距的环节
6.1 消息队列:选型与三个消息问题
场景设计题是快手面试的区分度所在。我二面时被要求围绕“用户上传视频后,系统要做转码、审核、通知等多个步骤”来设计消息方案。这种题本质是考 MQ。你需要先明确为什么用 MQ,主要是为了异步、削峰、解耦。面试官会顺势问“你项目里用的什么 MQ,为什么选它”。如果项目里用的 Kafka,可以说它吞吐量高、适合日志和流式处理,但事务和消息可靠性方面需要自己多注意;如果用 RocketMQ,可以说它事务消息和延迟消息支持更好,适合业务场景。重点是给出的理由要和项目场景匹配,不要为了秀而硬说。
消息可靠性是必考的三连问:怎么保证消息不丢失、怎么保证不重复消费、怎么保证顺序消费。不丢失要分三段说:生产端通过确认机制,acks=all并开启重试;Broker 端通过刷盘策略和副本机制防止宕机丢数据;消费端处理完业务逻辑后再提交 offset。不重复消费没法彻底避免,只能保证消费的幂等性,比如用唯一业务号去重、用数据库唯一索引约束、用 Redis setnx 做消费标记。顺序消费要分场景:全局顺序很难做,通常只需要保证分区内顺序,Kafka 里同一个 key 的消息会进同一个 partition,消费者单线程消费该分区,就能保证顺序。
6.2 分布式事务与幂等
分布式事务也是高频。常见的方案有三类:2PC(两阶段提交)、TCC(Try-Confirm-Cancel)、最终一致性。我建议你重点准备 TCC 和基于 MQ 的最终一致性,因为这两个在实际项目中用得多。TCC 要能说清三个阶段的动作:Try 阶段预留资源、Confirm 阶段真正执行、Cancel 阶段回滚释放资源。但也要诚实说出来 TCC 的问题:实现成本高、空回滚和悬挂问题要处理、业务侵入性强,所以不是所有场景都适合。
基于 MQ 的最终一致性是更实用的方案。基本流程是:生产者本地事务和消息发送放在同一个事务里,事务提交后消息才可见;消费者消费消息执行业务,如果失败就重试,多次重试仍失败则进入死信队列,由人工或补偿任务处理。这种方案的一致性不是强一致的,存在一个时间窗口,但大多数业务场景能接受。面试时要把这个权衡讲清楚,面试官会认可你懂业务和技术之间的取舍。
6.3 秒杀系统设计
快手业务里活动场景很多,秒杀设计题基本避不开。我当时被问的是“如果要做一场大促秒杀,你会怎么设计”。这种题不要一上来就写代码,先把核心问题列出来:瞬时流量大、超卖、接口防刷、活动页缓存。
流量削峰这块,我会先说前端层面:按钮置灰、答题验证码、限制用户请求频率。网关层面:按用户维度做限流,比如单用户每秒最多 5 个请求,同时做全局漏斗限流。真正到后端服务的流量已经小很多了。库存扣减是秒杀的核心,这里要避免“先查库存再更新”这种非原子操作,应该用 Redis 的DECR或 Lua 脚本原子扣减库存,扣减成功后再发送 MQ 消息,由下游异步去创建订单。数据库最终库存也要校验,防止 Redis 和数据库不一致。
超卖防止的关键是数据库层扣减库存的 SQL 必须带条件:UPDATE stock SET count = count - 1 WHERE sku_id = ? AND count > 0,同时检查受影响行数是否为 1,否则就说明库存已经不足。接口防刷可以用用户 ID 加上活动 ID 做唯一键,让每个用户只能参与一次。
提示:场景设计题不需要给出“完美的方案”,面试官更看重你的思考框架。建议按“流量入口 -> 缓存 -> 队列 -> 数据库 -> 最终一致性”这条链路去组织答案,逻辑自洽比炫技重要得多。
7. 算法手撕与项目复盘:高压下的基本功
7.1 快手算法题考什么
快手手撕算法的难度在我面的几家里属于中等偏上,不像字节那样经常出 hard,但也不是随便冒个泡排序就能过的。常见的有:反转链表、无重复字符的最长子串、LRU 缓存、快排、TopK 问题、合并两个有序数组、二叉树层序遍历这些。我第一轮就被要求手写“快速排序”,当时我愣了一下,觉得这也太基础了,但写的时候才发现,如果平时已经习惯了用Arrays.sort,突然让你手写原地快排,边界处理很容易出问题。
这里我给你一个提醒:基础排序算法必须能闭着眼睛写,不能只说思路。快速排序考的是:选基准值、分区函数、递归终止条件,尤其是分区函数里左右指针的边界,以及选择基准时的退化问题,比如数组已经有序时固定选第一个元素,复杂度会退化成 O(n²),所以要用三数取中法或者随机选基准。TopK 问题也很常考,一般用堆或者快排 partition 思想,如果你能给出PriorityQueue和快速选择两种方案,并比较时间和空间复杂度,就很稳了。
7.2 手撕代码的三个习惯
我总结了三个手撕代码的习惯,在快手的面试里帮我稳住了节奏。
第一,动手前先和面试官确认输入输出。比如题目说“返回第 K 大的数”,你需要确认 K 的范围、数组是否可能为空、是否包含负数。这种沟通不是废话,面试官会通过这个过程观察你写代码前的思考习惯。
第二,写完之后主动拿一个边界 case 自测。比如单节点链表、空树、数组只有一个元素、目标值不存在等。我写二叉树层序遍历的时候,主动说“如果根节点是 null 应该返回空列表而不是报空指针”,面试官明显点了点头。
第三,过一遍时间复杂度。如果算法写完你能自己说出“这里每个节点最多入队出队一次,所以是 O(n),空间复杂度是 O(n),最坏情况下队列里有一层所有节点”,这就是加分项。不要等面试官来问,主动说更显专业。
7.3 项目复盘的 STAR 讲法
面试前一定要把项目用 STAR 法整理一遍:Situation(背景)、Task(任务)、Action(行动)、Result(结果)。但大厂面试不止要求完整讲出这四个要素,更看重你在项目里的个人贡献和思考。我见过很多人讲项目,开口就是“我们项目用了 Spring Cloud 全家桶”,面试官根本不感兴趣。更好的做法是用冲突引导:项目里遇到什么问题 -> 我通过对比了几种方案 -> 我选了其中一种是因为什么 -> 实现后效果如何、这个方案有什么隐患。
我自己的一个项目是“前后端分离的运营后台系统”,后端是 Spring Boot,前端是 Vue,部署时用了 Jenkins 做 Maven 构建和自动化发布。面试官对这个项目连问了三个问题:权限怎么做、数据隔离怎么做、接口跨域怎么处理。这三个都是前后端分离项目里特别容易踩坑的点。跨域我答了用 Nginx 反向代理统一入口,这样浏览器请求的是同源地址,后端不需要单独开 CORS。这种细节比堆项目数量要有用得多。
还有一个小技巧是,项目里如果你用了 RuoYi 这类开源框架做二次开发,一定不要在面试里说“我们就是基于 RuoYi 改的”,因为面试官会默认你没有自己做过设计。要讲清楚你在框架基础上做了哪些核心定制、解决了什么问题,这样反而能体现你的工程能力。
7.4 反问环节问什么
反问环节别浪费,也不要只问“薪资多少”“加班多吗”这种问题。我一般会问三个方向:团队目前用的技术栈和中间件有哪些,当前业务遇到的最大技术挑战是什么,团队对新人成长的期望是什么。这些问题的好处是既显得你有上进心,也能通过对方的回答判断这个团队是否适合你。我问技术栈时,面试官提到了他们正在做直播互动场景的性能优化,我顺着这个话题聊了几句自己的看法,这轮面试的氛围明显轻松很多。
反面典型的反问是“你们有什么技术栈”“你们做什么业务”,这种问题在技术面里问出来,会让人觉得你根本没做功课。可以通过公司的技术博客、公开分享提前了解大概方向,哪怕只知道一个大概,也能让对方觉得你是认真准备的。
最后分享一个我自己的体会:面经最大的价值不是让你背答案,而是让你知道该往哪个方向深挖。我之前准备面试时,把大量时间花在背各种八股文结论上,结果第一次模拟面试就被问得哑口无言。后来我把每个高频问题都当成源码去读一遍,结合自己项目里踩过的坑去理解,效果比刷题好得多。尤其是 Java 并发和 Spring 这两块,光是“知道结论”和“能讲清楚为什么”之间的差距,就是普通工程师和高级工程师之间的差距。如果你也是刚起步的 Java 后端,建议从 HashMap 源码、ConcurrentHashMap 的 put 流程、Spring Bean 生命周期这三个点开始死磕,打好基本功,再复杂的面试题也是从这个底子上长出来的。