1. 项目概述与核心思路拆解
做这份汇总之前,先聊聊我自己的经历。前两年我参与过几轮一线大厂Java岗的面试,也帮不少朋友做过模拟面试,亲眼看着"八股文"这三个字从贬义词变成大家又爱又恨的保命符。你问我现在大厂还考不考八股文?我可以很明确地说:考,而且考得比以前更细、更深。但考察方式和五六年前已经完全不同了。
以前面试官喜欢直接问"HashMap底层原理讲一下",现在更多是给你一个线上场景,让你分析为什么会CPU飙升、为什么接口突然超时,然后顺藤摸瓜追问到HashMap、并发、JVM。换句话说,八股文依然是面试的骨架,但大厂现在真正想验证的是你有没有把骨架变成自己的肌肉记忆。
这份汇总的定位,就是帮你把面试中最常出现的硬核知识点按底层原理、高频追问、实战场景三个维度拆开。它既适合准备跳槽的Java工程师,也适合刚工作一两年、想系统梳理基础的朋友。不管你是面大厂还是中型互联网公司,下面这些考点基本绕不开。
1.1 为什么八股文仍然是面试硬通货
我在某家公司做技术面试官时发现一个现象:候选人项目经历写得天花乱坠,但一追问"你项目里Redis为什么用跳表不用红黑树""你这个接口幂等怎么保证的",很多人就答不上来。不是他们不努力,而是大多数人的项目经验停留在"会用API"的层面,底层原理基本属于黑盒。
八股文考察的本质,其实是基本功的扎实程度和抽象思维的能力。Java面试不会直接考你工程能力,因为短时间没法验证,只能通过你对集合、并发、JVM这些基础组件的理解深度来间接判断。你如果能把HashMap的扩容机制、AQS的同步队列、JVM的GC日志这类底层细节讲清楚,面试官才会相信你有能力排查线上复杂问题。
所以我一直跟身边人强调:别把八股文当成死记硬背的负担,它其实是一套经过大厂反复验证的"底层能力体检表"。你把这张表里的项目逐项吃透,工程能力自然会跟着涨。
1.2 大厂面试环节的底层逻辑
一线大厂的Java面试,通常有三到五轮技术面加一轮HR面,每一轮的考察目标完全不同。第一轮一般以基础八股为主,重点筛掉基本功不过关的人;第二轮开始结合项目深挖,考察你解决问题的思路;第三轮更多是系统设计和架构视野,看你能不能把零散知识点串成体系。
有一个很关键的认知很多人没转过弯来:面试官其实不是要找"什么都会"的人,而是找"教得会、聊得来"的人。所以你回答问题时的逻辑层次比答案本身更重要。一个标准的回答框架是:先给结论,再讲核心原理,最后补充一两个细节或反例。这套框架在下面所有考点里都适用。
2. Java核心基础与集合框架高频考点
2.1 集合类源码与设计思想
集合框架是Java面试的必考区域,但很少有人真正讲透。面试官问"ArrayList和LinkedList区别"时,大多数人只会答"一个数组一个链表",这个回答在五年前可能及格,现在基本属于送人头。
正确的打开方式是分三个层次回答:
- 数据结构层:ArrayList底层是Object数组,LinkedList底层是双向链表。数组支持O(1)随机访问,链表插入删除理论上是O(1),但需要遍历找到位置。
- 扩容与性能层:ArrayList默认容量10,扩容时按1.5倍增长,扩容需要Arrays.copyOf迁移数据,所以频繁add的批量场景应该预估容量。LinkedList每个节点还存prev和next引用,内存占用比ArrayList高不少。
- 应用场景层:随机查询多选ArrayList,频繁头尾插入删除选LinkedList。但实际工程里LinkedList用得很少,因为ArrayList的局部性原理对CPU缓存更友好。
我面试时特别喜欢追问一个细节:HashMap的加载因子为什么是0.75。这个问题能筛掉一大半背答案的人。0.75不是随便拍的数,它是空间和时间成本的折中。加载因子越大,空间利用率越高,但哈希冲突概率增大,链表变长,查询效率下降;加载因子越小,扩容频繁,空间浪费多。0.75在数学期望上可以让哈希冲突概率控制在较低水平,配合泊松分布的计算,链表长度达到8的概率已经低于千万分之一。
2.2 HashMap扩容与并发场景的坑
HashMap在Java 8之后引入了红黑树优化,链表长度超过8且数组长度大于64时转为红黑树,长度低于6时再退化为链表。这里有个面试官常挖的坑:为什么是8和6而不是7和7?因为中间留了缓冲,避免元素在7和8之间反复横跳引发不必要的树化和反树化。
再往下挖就是resize过程。HashMap扩容时,元素在新数组中的位置要么是原索引,要么是"原索引+旧容量"。这个规律源于(n - 1) & hash的位运算特性,扩容时n变成原来的两倍,n-1的高位多了一个1,所以元素位置只会发生这两种变化。Java 8之后扩容不再rehash,性能提升明显,也消除了Java 7中并发扩容形成环链的问题。
但注意,这并不意味着HashMap可以随便用在并发场景。多线程同时put时,可能出现数据覆盖、size计数不准确等问题。面试官追问到这里,标准答案是用ConcurrentHashMap。ConcurrentHashMap在Java 8里放弃了分段锁,改用CAS加synchronized锁住数组的头节点,锁粒度更细,并发度更高。它同样利用了红黑树优化,而且size()方法用了baseCount加CounterCell数组,避免了全局竞争。
这里分享一个面试中的表达技巧:当面试官问"HashMap线程安全吗"时,不要只说"不安全,用ConcurrentHashMap",要主动补一句"具体来说是数据覆盖和size不准,原因是put操作没有锁保护,多线程同时计算完哈希后写入同一个桶,后写覆盖先写"。这个细节能让面试官立刻知道你是真的看过源码,而不是背了网上答案。
3. 并发编程:从锁到内存模型
3.1 synchronized与AQS的本质
并发编程是Java面试的深水区,也是最容易拉开差距的地方。我先说synchronized。很多人只知道它是重量级锁,但Java 6之后synchronized经过锁升级优化,现在性能已经不输ReentrantLock了。
锁升级的完整路径是:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。偏向锁会记录线程ID,同一个线程反复进入同步块时不需要CAS;一旦有竞争,升级为轻量级锁,通过自旋尝试获取;自旋超过阈值或CPU核数过多时,升级为重量级锁,由操作系统管里。这里面试官经常问"自旋的优缺点",你要答出:自旋避免了线程切换的开销,但自旋期间线程一直占用CPU,如果锁被持有时间很长,自旋反而浪费CPU。
ReentrantLock则完全不同,它基于AQS(AbstractQueuedSynchronizer)实现。AQS核心是一个volatile修饰的state变量加一个CLH变体队列。拿非公平锁举例,线程尝试用CAS把state从0改成1,成功就拿到锁;失败则进入同步队列,前一个节点释放锁后会唤醒后继节点。公平锁和非公平锁的区别在于:非公平锁在进入队列之前会再抢一次锁,抢不到才排队;公平锁则直接排队。
我之前模拟面试时有个候选人的回答印象很深,他说"ReentrantLock底层就是内部类Sync继承了AQS"。这个方向对,但太浅了。更好的回答是讲清楚state的语义:可重入其实是通过state = state + 1实现的,同一个线程可以多次加锁,每次加锁state加1,释放一次减1,减到0才真正释放。而读写锁ReadWriteLock在AQS里用同一个state的高16位和低16位分别表示读锁和写锁的持有数,这种"一个变量拆成两半用"的设计非常精妙。
3.2 线程池参数与拒绝策略实战
线程池在面试和工作中都极其高频。标准七参数:核心线程数、最大线程数、空闲时间、时间单位、阻塞队列、线程工厂、拒绝策略。面试官最爱问的是"核心线程数和最大线程数怎么设置"。
这个问题没有唯一正确答案,但你需要表现出自己理解任务类型。CPU密集型任务,核心线程数设置为CPU核数加一或加二,因为主要消耗CPU,线程太多反而反复上下文切换;IO密集型任务,核心线程数可以设置为CPU核数的两倍左右,因为IO等待期间线程可以让出CPU给别的线程。
另外一个高频追问是执行流程。当任务提交时,先判断核心线程是否空闲,有空闲则创建核心线程执行;没有则放入阻塞队列;队列满了再创建非核心线程;线程数达到最大值且队列也满,才触发拒绝策略。这个顺序很多人记反,我建议用生活类比记:公司里核心员工先干活,活太多了进任务清单排队,清单也排满了,再招临时员工,临时员工也忙不过来,就只能拒接新需求了。
拒绝策略有四种,实际开发中AbortPolicy(直接抛异常)最常用,因为它能让问题及时暴露。但我见过一个更好的方案,是自定义拒绝策略,把拒绝的任务写入本地表或者MQ,等高峰期过了再异步处理。面试时能说出这种方案,说明你有线上经验,很加分。
注意:阿里Java开发手册里禁止使用Executors创建线程池,原因是FixedThreadPool和SingleThreadPool的阻塞队列是无界LinkedBlockingQueue,任务堆积过多会OOM。CachedThreadPool的最大线程数是Integer.MAX_VALUE,同样有OOM风险。每次问到这个点,我都会强调:面试时主动提这个规范,会让面试官觉得你平时有注意工程规范。
4. JVM与性能调优:内存布局与GC选型
4.1 JVM内存区域与对象分配
JVM几乎是所有大厂面试的压轴考点。先从内存区域说起:程序计数器、虚拟机栈、本地方法栈、堆、方法区(以及Java 8之后的元空间)。面试官常用连环问:哪些区域会OOM?哪些线程私有?哪些共享?
线程私有的部分(程序计数器、虚拟机栈、本地方法栈)一般不会OOM,栈溢出通常是StackOverflowError;线程共享的堆和方法区才是OOM的高发区。Java 8把方法区改为元空间后,用的是本地内存,默认情况下元空间不会OOM,除非显式设置MaxMetaspaceSize。这个设计变化,本质上是避免方法区和永久代经常出现的Full GC问题。
对象分配规则也值得展开。绝大多数对象在新生代的Eden区分配,Eden不足时触发Minor GC。大对象直接进入老年代,这个"大"由-XX:PretenureSizeThreshold控制。长期存活的对象经过15次Minor GC后进入老年代,15对应-XX:MaxTenuringThreshold,用4位存储,所以最大值是15。这里有个细节:动态年龄判定。如果Survivor区中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,不必等到15岁。
讲这些的时候,最好配合一次实际的JVM参数实践。我习惯在模拟面试时让候选人设计一个线上Spring Boot应用的JVM参数,比如:
java -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -Xlog:gc*Xms和Xmx设置相同,避免运行期动态扩容带来的性能抖动;新生代2G,给Survivor区留够空间;G1GC是现在互联网公司的默认选择。能把这组参数逐项解释清楚,比背十个GC概念管用得多。
4.2 垃圾回收器选型与调优参数
GC这块,面试官常考的点已经从"讲一下CMS和G1区别"进化到"线上Full GC频繁你怎么排查"。先说结论:CMS注重低停顿,使用标记-清除算法,会有浮动垃圾和内存碎片;G1通过把堆划分为Region,用可达性分析和预测模型实现可预测的停顿时间。
G1的Region设计非常有趣。Region分为Eden、Survivor、Humongous和Old四种类型,大对象直接进Humongous Region。G1的回收过程包括Young GC、并发标记周期和Mixed GC。最核心的是它维护了一个"停顿预测模型",根据历史数据估算回收每个Region要花多长时间,然后优先回收收益最大的Region,这就是"垃圾优先"名字的来源。
线上排查Full GC,我提供一个经过验证的排查路径:
- 先看GC日志,确认Full GC频率和耗时:
jstat -gcutil <pid> 1000- 用jmap dump堆,分析大对象和类加载情况:
jmap -dump:format=b,file=heap.hprof <pid>- 用MAT或VisualVM分析堆中哪些对象占用内存最多。
- 检查代码层,通常有三个元凶:集合没有释放且无限增长、缓存没有过期策略、一次查询加载了过多的数据。
我遇到过最典型的案例是,某服务内存一直缓慢增长,最终一个节点OOM。排查发现是本地缓存用了Guava的LoadingCache,但设置了maximumSize(100000)和expireAfterWrite(10, TimeUnit.MINUTES)后,数据量依然增长到几百万。最后定位是Key用了对象导致缓存没法命中过期策略。这种问题,面试官问"怎么查线上OOM"时,你能讲出这个案例,比背任何八股都有说服力。
5. Spring与微服务生态高频问题
5.1 Spring Bean生命周期与循环依赖
Spring不仅是面试重点,也是实际开发的基石。Bean生命周期问题,面试官喜欢从"什么时候执行构造方法、什么时候注入属性、什么时候调用afterPropertiesSet"这种顺序题开始,慢慢深入到循环依赖这种源码级考点。
Bean生命周期我习惯用一个顺序链记忆:实例化 -> 属性填充 -> Aware接口回调 -> BeanPostProcessor前置处理 -> InitializingBean / init-method -> BeanPostProcessor后置处理 -> 使用Bean -> DisposableBean / destroy-method。其中BeanPostProcessor是整个Spring扩展机制的核心,AOP代理就是在这条链上完成的。
循环依赖是真正的分水岭问题。Spring三级缓存解决的是单例Bean的循环依赖:
- 一级缓存:存放完整的单例Bean
- 二级缓存:存放早期暴露的Bean(已经实例化但未完成属性填充)
- 三级缓存:存放Bean工厂对象,用于提前生成代理对象
为什么需要三级缓存?因为AOP可能在属性填充之前就需要生成代理对象。如果只有一级缓存,A和B互相依赖时,A还没创建完就要注入B,B又依赖A,就死锁了。三级缓存里的ObjectFactory可以在B引用A时,提前把A的代理对象创建出来放到二级缓存。构造器注入的循环依赖没法解决,因为实例化阶段就需要对方存在,这个要答得干脆。
我面试时还喜欢追问"那为什么不用二级缓存,把三级缓存去掉行不行"。答案是:如果去掉三级缓存,所有Bean在创建早期都需要先生成AOP代理,即使没有循环依赖也不需要代理的场景下会白白多创建代理对象,影响性能。三级缓存的ObjectFactory是懒加载机制,只有真正发生循环依赖时才调用。
5.2 事务失效场景盘点
Spring事务是工程中真正的"坑王"。很多人以为加了@Transactional就能回滚,实际上有一堆失效场景。我把高频失效原因整理成一个速查表:
| 失效场景 | 原因 | 解决方式 |
|---|---|---|
| 方法内部自调用 | this调用,没走Spring代理 | 注入自身代理,或者拆到另一个Bean |
| 方法不是public | Spring只能代理public方法(CGLIB可以代理非public但注解不生效) | 改成public |
| 异常被捕获 | 事务感知不到异常 | 重新抛出或手动rollback |
| 异常类型不对 | RuntimeException和Error才会回滚,受检异常默认不回滚 | 设置rollbackFor |
| 数据库引擎不支持事务 | MyISAM不支持 | 换InnoDB |
| 传播行为错误 | REQUIRED_NEW或NOT_SUPPORTED会让当前事务挂起或取消 | 明确传播行为 |
这里重点说自调用问题。模拟面试时,十个人有八个栽在这里。代码如下:
public void process() { updateData(); } @Transactional public void updateData() { // 数据库操作 }process方法内部直接调用updateData,updateData上的@Transactional根本不会被代理拦截,因为它走的是this指针,而不是Spring容器中的代理对象。解决方式有几种,最常见的是把updateData拆到另一个Service类里,或者注入自身代理。面试时能主动分析"Spring事务本质是AOP代理",再加一句"CGLIB代理和JDK动态代理的实现差异",基本就能把这个考点吃透。
另外提一句,Spring Boot 2.x之后默认使用CGLIB代理,即使是类不是接口也能代理。但CGLIB代理在代理没有实现任何接口的类时,子类会覆盖目标类的所有非final方法。如果目标方法被final修饰,CGLIB也没办法代理,事务同样失效。
6. 数据库与缓存:MySQL与Redis高频考点
6.1 索引原理与SQL优化
MySQL在Java面试中的权重越来越高,因为后端开发每天都在跟数据库打交道。先讲索引。面试必问B+树,为什么不是B树、红黑树或哈希索引?核心是磁盘IO次数。B+树所有数据都在叶子节点,叶子节点之间通过链表相连,范围查询时只需遍历链表;B树的非叶子节点也存数据,导致每层能容纳的索引项更少,树更高,IO更多。红黑树是二叉树,同样数据量下树深远大于B+树,磁盘IO次数完全没法看。
索引优化方面,最值钱的规则是最左前缀原则。联合索引 (a, b, c),查询条件只有b时走不了这个索引,只有a时能走,是a和b时能走但只能用到a和b上的索引优化能力。很多人不知道的是,MySQL 8.0支持了索引跳跃扫描,在某些场景下可以跳过最左列,但实际效果有限,不要指望它解决所有问题。
SQL优化的关注点,我总结为"三看":
- 看执行计划:用
EXPLAIN看type列,all是全表扫描,ref是普通索引,const是主键或唯一索引,性能从差到好依次是all、index、range、ref、eq_ref、const。 - 看是否回表:覆盖索引能避免回表,select字段尽量只挑需要的列,不要无脑
select *。 - 看是否产生临时表和文件排序:
EXPLAIN中Using filesort对排序性能影响很大,要避免。
6.2 事务隔离级别与MVCC
数据库隔离级别是另一个必考点。标准有四种:读未提交、读已提交、可重复读、串行化。MySQL默认是可重复读,但这和标准SQL有点不同,因为MySQL的InnoDB通过MVCC在可重复读下已经规避了大部分幻读问题。
MVCC的核心是隐藏列和undo log。InnoDB每行数据有三个隐藏字段:trx_id记录最后一次修改事务ID,roll_pointer指向undo log版本链,db_row_id是行ID。读操作通过ReadView判断当前事务能看到哪个版本。RR和RC生成ReadView的时机不同:RC每次快照读都生成新ReadView,所以能读到别的事务已提交的数据;RR只在第一次快照读时生成,后续都复用同一个ReadView,所以避免了不可重复读。
有趣的是,RR下的快照读不会产生幻读,但当前读(如 select ... for update)还是会幻读,需要间隙锁(gap lock)或next-key lock来防止。这个细节很多人答不上来,面试官抛出"RR到底解没解决幻读"时,如果你能区分快照读和当前读,给出"快照读通过MVCC解决,当前读通过next-key lock解决"的答案,基本上就是标准回答的高配版。
6.3 Redis缓存穿透与一致性
Redis相关的问题里,"缓存穿透、缓存击穿、缓存雪崩"三兄弟出现频率极高。先分清概念:
- 缓存穿透:查询一个不存在的key,缓存查不到,请求直接打到数据库。
- 缓存击穿:某个热点key过期瞬间,大量请求同时打到数据库。
- 缓存雪崩:大量key同时过期,或者Redis宕机,数据库被压垮。
解决穿透,可以缓存空值,但要设置较短的过期时间;更好的方案是布隆过滤器,把所有可能存在的数据key提前加进去,查询key不存在时直接返回。布隆过滤器有个特点:有可能误判"存在",但绝不可能误判"不存在",所以它能挡住一大部分空查询。
击穿的解决方式是用互斥锁。缓存失效时,只允许一个线程去查数据库并重建缓存,其他线程等待或降级处理。用Redis实现分布式锁时,可以用SET NX EX,但要注意锁的续期问题,避免业务执行时间过长锁自动过期。
缓存一致性是更复杂的话题。常见的Cache Aside模式是:读时先读缓存,读不到读数据库再回填;写时先更新数据库,再删除缓存。为什么是删缓存而不是更新缓存?因为更新缓存可能把旧数据写入,而且删缓存比更新缓存更简单,下一次读请求会回填最新值。不一致窗口仍然存在,业界方案包括延迟双删、消息队列异步删除、基于binlog的订阅同步。面试时能说出binlog方案,展示你对Canal等工具的认知,会很加分。
7. 消息队列与分布式理论
7.1 消息队列的可靠性
现在Java面试几乎必带"用过MQ吗"这个问题。如果你项目里有消息中间件,会被追问可靠性问题。消息从生产到消费,经过三个阶段,任何一个阶段丢消息都会出问题:
- 生产阶段:确认机制保证消息到了Broker才返回成功。
- Broker存储阶段:消费完更新offset前,消息不能丢。Kafka的副本机制、ISR列表、acks都围绕这个。
- 消费阶段:处理完业务逻辑后才提交offset,避免消息处理失败就丢失。
Kafka有个关键概念是ISR(In-Service Replica),Leader维护一个同步副本列表。当acks=all时,Leader会等ISR中所有副本都写入成功后才确认。面试官常问"如果ISR同步副本都挂了怎么办",答案是Leader会等min.insync.replicas指定数量的副本确认,消费者可以指定isolation.level=read_committed来避免读取未提交的数据。
RocketMQ则在事务消息方面做得更完善。事务消息先发一个half消息,Broker先持久化但不可消费,然后执行本地事务,根据结果提交或回滚。如果本地事务执行中宕机,Broker会通过回查机制确认事务状态。这个机制本质是"两阶段提交的消息版",理解了这一点,面试基本不会卡壳。
7.2 分布式事务与CAP
分布式事务是高级岗的分水岭。先说CAP理论:一致性、可用性、分区容错性三者最多满足两个。实际分布式系统必须选择P,然后在C和A之间权衡。但注意,CAP里的C和A是"强一致"和"完全可用",而实际工程可以用最终一致性来"同时拥有"弱C和弱A,这也是很多系统看起来既有一致性又有可用性的原因。
BASE理论是实践导向:基本可用、软状态、最终一致。分布式事务落地,常见方案有:
- 2PC(两阶段提交):有协调者,第一阶段prepare,第二阶段commit/rollback。优点是强一致,缺点是阻塞和同步性能差。
- TCC(Try-Confirm-Cancel):把每个操作拆成Try预留资源、Confirm提交、Cancel回滚三段。需要业务方实现,侵入性强。
- 本地消息表:在业务库建消息表,事务中同时写业务数据和消息,由定时任务扫描发送。实现简单,是很多公司的基础方案。
- MQ事务消息:利用MQ的half消息加回查机制。
面试官问"你项目里的分布式事务怎么实现"时,最忌讳直接说"用了Seata"。你要先说业务场景是什么,对一致性要求多高,再说明为什么选这个方案。比如订单支付场景,如果允许短暂的延迟一致,用MQ事务消息加幂等操作就够了;如果必须强一致,可能要上TCC。这种"场景驱动选型"的思路,比背一堆方案名称值钱得多。
8. 常见问题与面试策略
8.1 面试回答的表达框架
技术面试高手的回答模式,我观察下来是高度一致的,可以用"结论-原理-案例"三步来概括。
第一步,给结论。面试官问"讲讲HashMap",先说"HashMap底层是数组加链表加红黑树,Java 8之后引入红黑树优化"。这会让面试官立刻抓住你回答的框架。
第二步,讲原理。展开说为什么是8,为什么要红黑树,扩容过程怎么实现。原理部分要控制节奏,说到关键处留一个钩子,比如"HashMap在并发下会有数据覆盖,这个问题ConcurrentHashMap通过CAS解决了",引导面试官往下问。
第三步,举案例。结合自己做过的项目,讲"我遇到过什么问题,怎么定位,怎么解决"。这一步是普通候选人和优秀候选人最明显的差距。有经验的人会用线上真实case支撑概念,面试官听到真实案例会非常投入。
模拟面试中我发现一个普遍问题:候选人回答问题太发散,经常答到一半丢了主线。这时候要主动拉回来,比如"刚才说的是A部分,回到B部分的结论上"。这种自我控制能力,面试官非常看重。
8.2 高频踩坑与避坑清单
这两年我梳理了很多面试失败案例,整理几个高频踩坑点,也算给准备面试的朋友提个醒:
- 在不会的问题上硬撑。碰到完全没接触过的领域,宁可直接说"这个方向我没深入做过,但我了解相关的是XXX",也不要现场编。面试官最反感的是明显在编答案。
- 只背结论不背原理。比如知道"用ConcurrentHashMap",但说不清楚CAS和synchronized结合的原理。大厂算法题可以刷,八股文一定要结合源码理解。
- 项目细节准备不足。简历里写了Redis分布式锁,至少要能回答它的续期问题、集群模式下的安全性、为什么会存在主从切换锁丢失。写上去的项目,要经得起拷问。
- 对JVM和MySQL参数不清楚。现在很多面试官喜欢问实际配置,比如"你线上JVM堆多大、新生代多大、用的什么GC"。你应该平时就记录自己项目的真实配置,并在面试时讲出来。
- 忽略现场手写代码。现在大厂面试经常让现场手写线程池的简单实现、手写LRU缓存,或者给一道算法题。八股文复习之余,LeetCode高频题至少要刷到一百题以上,重点练字符串、二叉树、动态规划和数组。
我自己在组织面试时,会把候选人分三类:背答案的、懂原理的、能实战的。大厂最终想要的一定是第三类,但多数人连第二类都做不到。八股文复习的真正目的,不是让你在考官面前表演背诵,而是让你在面试那一刻大脑里有足够多的"底层模型",遇到任何变体问题都能现场推出来。
最后分享一个我个人的复习小习惯:每次面试完,把被问到的问题按"答得好/答得一般/完全不会"三档记录。答得一般的题目,当天晚上去找源码或者看权威文档,用自己语言重新整理一遍。坚持两到三个月,你会发现面试时能回答出的内容深度,会有一个明显的跃升。这个方法我推荐给过不少人,反馈都很正向。面试这件事,说到底就是把基础功底和表达逻辑练到肌肉记忆,剩下的,交给临场发挥就好。