说个真心话2023年的Java面试八股文已经卷到不是“背不背”的问题而是“背到什么深度、能不能现场推导”的问题。我梳理这份内容的契机是自己在跳槽冲刺阶段把市面上的Java面试突击班、大厂面经和真题做了大量拆解最终浓缩成一份可以通宵过完的“Alibaba面试通关手册”。这篇不打算让你机械背题而是把高频考点后面的原理链路、答法框架和面试官连环追问的落点都铺开帮你在面对追问时不至于卡壳。这篇东西适合谁看呢一类是三年左右经验、准备冲大厂的同学另一类是工作五年以上但觉得知识体系碎片化、想系统回顾一遍的老兵。看完之后你至少能清楚两件事Java面试核心到底考哪几个模块以及每个考点常规的答题路径能延伸到多远。1. Java基础集合、泛型与String高频考点复盘基础部分看起来简单实际上是整场面试的地基。很多候选人挂在二面不是因为他不懂分布式而是HashMap被追问到第三层就答不上了。基础题的价值在于面试官可以通过连环追问快速判断你是背题还是真有理解。1.1 HashMap为什么面试官永远绕不开它HashMap基本是每一场Java面试的暖场题因为它能串起的数据结构和算法知识实在太多了哈希算法、数组和链表的取舍、扩容机制、红黑树退化条件每一层都能顺藤摸瓜往下挖。标准的答题主线建议这样展开。首先是结构演进JDK 1.8之前是数组加链表JDK 1.8之后是数组加链表加红黑树。默认容量16负载因子0.75超过阈值就扩容扩容是翻倍。然后是hash计算key的hashCode先做一次低16位和高16位的异或运算也就是h ^ (h 16)目的是让高16位也参与数组下标的计算减少冲突。再讲put流程先判断table是否为空为空就先resize然后根据key的hash计算下标如果下标位置为空就创建新节点不为空就判断key是否相等相等则覆盖如果节点是TreeNode就走红黑树插入否则遍历链表找到相同key覆盖没有就尾插。这里有一个非常容易被追问的点为什么JDK 1.8把头插法改成了尾插法答案是并发扩容时头插法容易形成环形链表get时会出现死循环。虽然HashMap本来就不保证线程安全但这个问题引入的并发场景是面试官非常喜欢的考察点。再往下就是“为什么链表长度到8就转红黑树”。这个阈值不是拍脑袋定的它来自泊松分布。在负载因子0.75的情况下链表长度达到8的概率大约是千万分之六用8作为阈值既能避免红黑树过早介入带来的额外开销又能在极端冲突时保证查询性能。能把这个分布概率讲出来面试官会眼前一亮。还要知道树化有一个前置条件就是数组长度必须大于等于64否则优先扩容而不是树化因为扩容本身也能缓解冲突。关于线程安全问题我的回答思路通常分版本讲JDK 1.7的问题主要是并发put导致扩容时出现环形链表JDK 1.8的问题主要是数据覆盖和size计数不准。然后顺势引出ConcurrentHashMap的实现差异这才是真正的加分环节。1.2 ArrayList、LinkedList与集合选型陷阱ArrayList和LinkedList的对比题看着简单但面试官能问出很多细节。先说底层结构ArrayList是动态数组默认容量10扩容时新容量是oldCapacity加oldCapacity右移一位也就是1.5倍最后调用Arrays.copyOf把旧数组元素复制到新数组。LinkedList是双向链表每个节点维护prev和next两个指针。常见认知里“插入删除选LinkedList随机访问选ArrayList”这个结论实际工程中经常是陷阱。因为LinkedList的插入删除虽然拿到节点引用后是O(1)但业务中你大多数时候需要先遍历找到目标位置这个遍历是O(n)。而且LinkedList节点需要额外的指针开销内存占用远高于ArrayListCPU缓存局部性也差。所以真实项目里任何需要遍历操作的场景LinkedList的优势都不明显。面试官常接着问Vector和CopyOnWriteArrayList。Vector是把方法都用synchronized修饰线程安全但性能很差基本属于历史遗留。CopyOnWriteArrayList则是写时复制读操作完全不加锁写的时候先复制一个新数组在新数组上修改再整体替换引用适合读多写少的场景但写操作开销大而且读到的数据可能不是最新的。能把这个取舍关系说清楚说明你对并发集合有实际理解。1.3 String、StringBuilder与字符串池String的考点几乎都会出现在第一轮但它的深度足够你聊十分钟。String是final类底层在JDK 9之前是char[]JDK 9之后改成了byte[]并且加了一个编码标记字段目的就是为了压缩内存占用因为大部分字符串用Latin-1编码就够了。String不可变带来的好处要能讲出两条一是字符串常量池可以安全复用二是hash值可以缓存因为内容不变所以String非常适合做HashMap的key。这三个特点加在一起就是String设计的核心逻辑。StringBuilder和StringBuffer的区别也是必问项前者线程不安全但性能最好后者在关键方法上加了synchronized所以线程安全。这里有个细节很多人忽略在方法内拼接字符串编译器会自动优化成StringBuilder但在循环体内拼接编译器每次循环都可能新建StringBuilder所以要自己在循环外创建StringBuilder对象来避免频繁创建对象。关于字符串常量池一定要能画出String s1 abc和String s2 new String(abc)在内存中的区别。前者只在常量池创建一个对象后者除了常量池对象外还会在堆上创建一个新的String对象所以s1 s2的结果是false。intern()方法的作用则是把字符串放入常量池如果已有相同内容就返回常量池引用。2. 并发编程从synchronized到AQS的底层链路并发模块是Java面试的硬骨头也是区分候选人的关键分水岭。面试官问并发重点不是看你写了多少并发代码而是看你能不能把从CPU缓存、内存模型到JVM锁机制再到JUC工具类的完整链路讲明白。2.1 JMM与volatile的关键语义Java内存模型描述的是多线程场景下变量访问的规范。它抽象出主内存和线程工作内存两个概念线程对变量的所有读写操作都必须在工作内存中进行不能直接操作主内存。这带来三个问题可见性、原子性、有序性。volatile解决的是可见性和有序性不保证原子性。它的底层是内存屏障写volatile变量时JVM插入StoreStore和StoreLoad屏障强制把工作内存的值刷回主内存读volatile变量时插入LoadLoad和LoadLoad屏障强制从主内存读取最新值。同时volatile禁止指令重排序最经典的例子就是单例模式双重检查锁中的instance变量必须用volatile修饰否则可能出现构造函数初始化步骤被重排导致另一个线程拿到半成品对象的场景。面试官基本会追一句什么时候用volatile什么时候用synchronized我的答法是volatile只适合状态标记、开关控制这种对原子性没有要求的场景。如果操作是check-then-act比如先判断再自增或者读改写就必须用锁或者原子类。2.2 synchronized锁升级从偏向锁到重量级锁synchronized的锁升级是高频中的高频很多候选人知道四个锁状态的名字但不知道每个状态的触发条件和开销差异。JDK 1.6之后synchronized引入了锁升级机制状态顺序是无锁、偏向锁、轻量级锁、重量级锁。偏向锁的适用场景是同一线程反复进入同步块第一次拿到锁后会把线程ID记录在对象头里后续再进来就不需要CAS了。一旦出现其他线程竞争偏向锁撤销升级为轻量级锁轻量级锁通过CAS自旋尝试获取锁适合锁持有时间短的场景。自旋超过一定次数或者竞争线程太多就膨胀为重量级锁重量级锁依赖操作系统底层互斥量涉及用户态和内核态的切换开销最大。这里我要特别提醒一点如果你面试时提到JDK 15之后默认禁用了偏向锁会显得你对版本演进有跟进意识。原因是偏向锁在撤销时也要STW高并发场景下收益不大甚至有负收益。2.3 AQS看懂ReentrantLock就入门了AQS几乎是JUC的基石ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock全部基于它实现。理解AQS就等于拿下了JUC。AQS的核心构成是一个volatile int state字段加上一个FIFO双向等待队列。以ReentrantLock为例state既表示锁是否被持有也表示重入次数每次重入state加1每次释放state减1减到0才真正释放锁。公平锁和非公平锁的区别是面试官最爱问的。非公平锁在lock时会先直接CAS尝试把state从0改为1完全不看等待队列中是否有人直接抢公平锁在任何情况下都先检查等待队列如果队列非空就必须排队。这里可以补充一句话非公平锁的吞吐量通常高于公平锁因为它减少了线程唤醒和上下文切换的开销这也是ReentrantLock默认使用非公平锁的原因。源码层面的几个关键方法也要能说出来tryAcquire、tryRelease是给子类实现的模板方法acquireQueued是线程入队后自旋等待的逻辑unparkSuccessor负责唤醒下一个等待线程。获取锁失败的线程会被包装成Node放入队尾然后在一个循环里检查前驱是不是头节点如果是就尝试获取锁否则阻塞自己等待前驱节点释放锁后唤醒。2.4 ThreadLocal内存泄漏的根源和规避ThreadLocal的八股文套路非常固定但真正理解它的人在面试中并不多。核心原理是每个Thread内部都持有一个ThreadLocalMapmap的key是ThreadLocal对象value是你要存的变量。问题就出在key是弱引用value是强引用上。当ThreadLocal对象没有外部强引用时key会被GC回收变成null但value还挂在map里。如果持有map的线程是线程池中的长生命周期线程value就永远不会被回收形成内存泄漏。我见过线上事故就是用了ThreadLocal存用户信息没有remove导致高并发下内存持续上涨。规避方法很明确使用完ThreadLocal之后在finally块中调用remove。ThreadLocalMap在get时也会尝试清理key为null的脏条目但这是兜底机制不能依赖它。这里再补充一个知识点InheritableThreadLocal可以让子线程继承父线程的ThreadLocal值但在线程池里不可靠因为线程是复用的不会每次新建子线程。如果要在线程池里传递上下文可以用阿里的TransmittableThreadLocal组件。3. JVM内存与垃圾收集从运行时数据区到OOM排查JVM模块是所有Java面试的压舱石。面试官关心的是你有没有线上排查经验能不能把运行时数据区、垃圾收集原理、故障排查工具串成一套完整的方法论。3.1 运行时数据区与对象分配运行时数据区要分线程共享和线程私有两个维度来讲。线程共享的是堆和方法区JDK 8之后方法区被元空间取代。线程私有的是虚拟机栈、本地方法栈和程序计数器其中程序计数器是唯一不会OOM的区域。对象分配流程是面试官喜欢考细节的地方。新对象优先在Eden区分配Eden区不够就触发Minor GC大对象会直接进入老年代目的是避免大对象在Eden和两个Survivor区之间来回复制经过一次Minor GC仍然存活的Survivor区放不下时对象会通过分配担保机制提前进入老年代。年龄阈值默认15达到阈值的对象也会晋升老年代。有一个问题特别容易被追问为什么JDK 8用元空间替代永久代因为永久代大小固定且很难调优容易出现永久代OOM元空间直接使用本地内存默认情况下只受机器物理内存限制而且元空间的垃圾回收逻辑更合理字符串常量池也顺势移到了堆中。3.2 垃圾收集器与GC调优思路垃圾收集器这部分重点掌握CMS和G1就够了。CMS的目标是最短停顿基于标记-清除算法过程分四步初始标记STW很快、并发标记、重新标记STW较快、并发清除。它的两个致命弱点必须记住一是标记-清除产生内存碎片二是并发阶段占用CPU资源可能导致吞吐量下降如果并发模式失败还会退化为Serial Old做Full GC。G1从JDK 9开始成为默认收集器它的设计理念是把堆划分成多个大小相等的Region新生代和老年代不再是物理隔离而是逻辑上的Region集合。G1通过一个优先队列维护每个Region的回收价值每次在允许的停顿时间内优先回收价值最大的Region这就是Garbage First名字的由来。调优核心参数是-XX:MaxGCPauseMillis但它不能被盲目调小否则GC频率会急剧升高整体吞吐量反而下降。如果面试官问线上频繁Full GC怎么排查建议回答一套完整的流程而不是单个命令。我常用的顺序是先用top看CPU占用和负载再用jstat -gcutil看各代内存和GC频率接着用jmap -dump导出堆转储文件用MAT分析大对象引用链同时用jstack看线程栈有没有死锁或长时间阻塞。3.3 类加载机制双亲委派与打破类加载机制可以分两档回答。基础档类加载五个阶段是加载、验证、准备、解析、初始化。双亲委派的核心逻辑是当类加载器收到类加载请求时不会自己先加载而是委派给父加载器一直向上到启动类加载器父加载器加载不了才自己尝试。为什么需要双亲委派两个理由一是防止核心类被恶意替换比如java.lang.String不能由自定义类加载器随意加载二是避免同一个类被不同加载器重复加载保证类的唯一性。进阶档要能回答怎么打破双亲委派。例子要说三个SPI机制的线程上下文类加载器比如JDBCDriverManager由启动类加载器加载但驱动实现类在classpath中需要线程上下文类加载器来完成加载Tomcat的WebAppClassLoader会优先加载web应用自己lib下的类再委托给父加载器这是为了隔离多个应用之间的类版本冲突热部署场景通过新建自定义类加载器重新加载类来替换旧版本。4. 框架与中间件Spring、MySQL、Redis深水区框架与中间件是实战经验最密集的模块。2023年的面试趋势是面试官越来越不满足于“你用过”而是要求你把Spring启动链路讲清楚、把MySQL索引原理讲透彻、把Redis缓存一致性方案讲明白。4.1 Spring Bean生命周期与循环依赖Spring Bean的生命周期建议按这个主线记实例化通过构造器反射创建对象到属性填充再到初始化阶段最后是销毁。初始化阶段里最核心的是BeanPostProcessor机制它会在init-method前后各回调一次AOP代理就是在后置处理这个环节通过AbstractAutoProxyCreator生成的。循环依赖是Spring面试的必考点。回答的钥匙是三级缓存一级缓存存放已经完整初始化好的Bean二级缓存存放早期暴露的对象三级缓存存放ObjectFactory函数式接口每个Bean实例化后就会把ObjectFactory放进去。当A依赖B、B依赖A时A实例化后把ObjectFactory放入三级缓存填充属性时发现需要B于是创建BB创建过程中需要A此时从三级缓存拿到A的早期引用B完成了自己的初始化后放入一级缓存A继续完成后续的属性和初始化。这里一定要注意两个坑。一是只有单例模式的Bean支持循环依赖Prototype和构造器注入不行因为构造器注入在实例化阶段就需要依赖对象此时对象还没放入缓存。二是如果A在循环依赖中需要AOP代理三级缓存中的lambda表达式会提前生成代理对象所以拿到的是正确的代理这个机制是Spring设计三级缓存而不是两级缓存的重要原因。4.2 MySQL索引与事务隔离MySQL八股文的权重索引和事务占据半壁江山。B树为什么适合做索引要从几个维度回答叶子节点存放真实数据并且通过双向链表连接支持范围查询的顺序遍历所有数据都在叶子节点查询复杂度稳定树的高度很低三层B树可以支撑千万级数据量。索引失效的场景要能一口气说出最左前缀原则、范围查询右侧列失效、对索引列使用函数、隐式类型转换、like以%开头、or条件中包含非索引列。这里要提醒一句实际开发中不是“索引失效”这么简单优化器可能会因为回表成本高、基数低等原因主动选择全表扫描所以还要会看EXPLAIN输出中的type和extra字段。事务隔离级别方面InnoDB默认是可重复读。四种隔离级别对应的并发问题要能背清楚读未提交有脏读风险读已提交解决脏读可重复读解决不可重复读串行化解决幻读。重点来了InnoDB在可重复读隔离级别下通过Next-Key Lock也就是记录锁加间隙锁的组合解决了幻读问题同时MVCC通过隐藏字段trx_id和roll_pointer配合undo log实现快照读。这里有个细节ReadView在RC和RR下的生成时机不同RC每次快照读都生成新的ReadView而RR只在第一次快照读时生成后续复用这才保证了可重复读。4.3 Redis缓存三大问题与一致性方案Redis相关问题的热点非常集中缓存穿透、缓存击穿、缓存雪崩和缓存一致性。缓存穿透是指查询一个不存在的数据缓存里没有所有请求都打到数据库。解决方案有三种用布隆过滤器在缓存前拦截不存在的数据对查询结果为空的数据也缓存一个空值并设置较短过期时间对请求参数做合法性校验。缓存击穿是热点key在过期瞬间被大量并发请求打穿到数据库。解决思路有互斥锁用setnx让一个线程去重建缓存其他线程等待还有逻辑过期方式设置一个逻辑过期时间异步去更新真实数据。缓存雪崩是大批key同时过期或者Redis宕机导致流量直击数据库。解决方式是过期时间加随机值打散过期时间或者本地缓存加Redis做多级缓存核心节点做高可用集群。缓存一致性是面试重灾区。最常用的方案是Cache Aside Pattern读的时候先读缓存缓存没有就读数据库再回填写的时候先更新数据库然后删缓存。注意是删除缓存而不是更新缓存因为更新缓存可能被并发旧数据覆盖。但删除缓存和更新数据库之间有一个短暂时间窗口另一个线程可能读到旧值。更完善的做法是监听MySQL binlog通过Canal异步删除或重建缓存或者用延时双删策略。5. 微服务与分布式Spring Cloud Alibaba实战2023年的Java岗位只要项目规模稍微大一点就离不开微服务。Spring Cloud Alibaba这套技术栈已经是国内面试的主题曲了阿里系面试官更是对这一块考察得非常细。5.1 注册中心与配置中心Nacos原理与选型Nacos是目前国内微服务的标配组件它把注册中心和配置中心合并成一个组件简化了部署和运维。和Eureka相比Nacos不仅支持AP模式还支持CP模式可以根据业务场景灵活切换。面试中Nacos的考点集中在临时实例和持久化实例的区别上。临时实例基于客户端心跳进行健康检查默认5秒发一次心跳15秒没收到心跳就标记为不健康30秒就移除实例这是典型的AP模式。持久化实例则是由服务端主动探测如果探测失败就标记为不健康这是CP模式。另外Nacos服务端会主动推送变更给客户端1.x版本用的是HTTP长轮询2.x版本改成了gRPC双向流推的实时性更高客户端维护成本也更低。配置中心的动态刷新机制也是高频考点。要点是客户端通过长轮询监听配置变更服务端有配置变更时就推送而配置变更会触发Spring的Environment刷新以及RefreshScope注解的Bean重建。如果写了这个注解配置一变Bean就会重建导致状态丢失这个坑值得在项目中仔细斟酌。5.2 分布式事务事务消息、TCC与Seata分布式事务是微服务面试中杀伤力比较大的题目面试官不会满足于你说“我们用了Seata”而是想看你对几种方案的辨析。首先要能把几种事务模式对比清楚。2PC两阶段提交准备和提交两个阶段都有同步阻塞协调者宕机整个事务会卡死。TCC是Try、Confirm、Cancel三阶段需要业务方自己实现补偿逻辑性能比2PC好但业务侵入性极强。本地消息表方案是把业务操作和消息写入放在同一个本地事务里通过定时任务轮询发送消息消费方必须幂等。RocketMQ事务消息则先把消息发成半消息执行本地事务后再提交或回滚。Seata的AT模式是阿里开源方案里主推的它的核心机制是解析SQL生成undo_log前置镜像和后置镜像通过全局锁保证写隔离业务代码不需要侵入式改造。但AT模式不适合所有场景大并发、长事务和资损敏感场景用TCC更可控因为AT模式的全局锁会持有到事务结束并发度高时会放大锁竞争。5.3 分布式锁Redis方案与ZooKeeper方案对比分布式锁是微服务面试的必选题。Redis分布式锁和ZooKeeper分布式锁是两大主流方案对比着回答是加分项。Redis分布式锁的经典实现是用SET key value NX PX timeout命令SETNX保证互斥EXPIRE防止死锁。但这里有一个经典问题命令必须是一条原子命令不能用SETNX加EXPIRE两条命令否则会出现加锁成功但过期时间设置失败导致的死锁。另一个难点是锁续期业务执行时间超过锁过期时间时锁会自动释放Redisson的看门狗机制会自动续期但它既解决了问题也引入了复杂度。Redlock算法在面试中可以作为加分项但要能说出它的争议。Redlock通过向多个Redis节点同时加锁超过半数成功才认为加锁成功。但它在工程上受到很多质疑核心是它依赖时钟同步而Redis节点的时钟可能发生偏移加上GC停顿会导致锁实际过期时间不一致。ZooKeeper实现分布式锁的原理是创建临时顺序节点每个客户端创建自己的节点后检查自己是不是序号最小的节点是就获取锁不是就监听前一个节点的删除事件。临时节点最大的优势是客户端宕机后节点自动消失不需要设置过期时间。方案在正确性和可靠性上有优势但ZooKeeper部署和运维成本高所以很多团队在非强一致场景下首选Redis方案。5.4 限流熔断降级Sentinel的核心机制Sentinel是阿里开源的高可用防护组件核心思路是定义资源、再对资源设置各种规则。和Hystrix相比Sentinel的优点是轻量、支持实时监控和细粒度流控关键区别是Hystrix基于线程池隔离线程切换开销大Sentinel默认是信号量隔离也就是计数器模式性能更好。Sentinel的流控模式包括QPS直接拒绝、线程数控制、关联流量控制流控效果包括快速失败、Warm Up预热、排队等待。熔断策略包括慢调用比例、异常比例和异常数三种。这些参数不是靠背的要在项目中实际配置过才能讲出细节。有一个经常被问的问题是Sentinel底层限流算法。Sentinel使用了滑动窗口计数器统计窗口被拆成多个小格子用内存空间换统计精度。它的核心类是ArrayMetric维护一个窗口数组通过时间戳定位当前窗口从而计算当前时间窗内的QPS或线程数。6. 备战策略与高频问答陷阱项目、算法与心态最后这个部分我想聊一些“八股之外”的东西。背八股只是准备的一部分如果你不会讲项目、不会做算法、心态不稳整个面试照样会很难受。6.1 项目介绍的STAR法则与亮点提炼面试官几乎开场就会让你自我介绍然后挑一个最有代表性的项目来展开。很多人习惯按流水账讲项目背景、功能模块、用了什么技术。这是最差的开场因为面试官根本抓不住重点。我推荐用STAR法则来组织项目回答。S是项目背景和业务痛点讲清楚为什么要做这个项目解决了什么问题T是你在项目里承担的具体职责A是你做的关键动作尤其是有技术挑战的部分R是量化结果比如性能提升多少、成本下降多少、可用性达到什么水平。这里最重要的是Action部分一定要讲出“为什么这样选”。比如“我用了Redis分布式锁而不是Zookeeper锁是因为这个场景对吞吐量要求很高而且业务允许短暂的不一致”。这句话能瞬间展示你的技术判断力。讲项目还应该提前给自己埋三个深挖点。面试官经常从你提的技术点深入追问比如你说了Redis缓存他会追缓存击穿怎么处理、一致性怎么保证、Redis挂了怎么办。所以项目经历和八股文是咬合在一起的项目里涉及的技术点对应的八股准备必须同步跟上。6.2 算法题准备节奏与高频题型大厂面试一般有一到两轮算法面不需要打竞赛难度LeetCode Hot 100加《剑指Offer》是底线。但刷题方式很关键我推荐按题型分类攻克而不是按题号顺序。高频分类大概是数组与双指针的盛水容器、三数之和链表的反转、环形链表、合并有序链表二叉树的层序遍历、最近公共祖先动态规划的爬楼梯、最长递增子序列栈和队列的有效括号、最小栈回溯的全排列、组合总和。刷题节奏上我不建议一天刷十几道追求数量而是要保证每道题都能在白板上讲清楚三层暴力解是什么、优化解是什么、时间空间复杂度是多少。面试时也不一定非要上来就写最优解先给出一个可运行的暴力解再逐步优化反而显得你思路严谨。6.3 八股文的正确打开方式理解、推导、表达关于八股文本身我最后说一个核心观点八股文不是让你死记硬背的它的价值是给你提供一张知识地图。你背下HashMap的原理不是为了在面试现场一字不差地复述而是为了将来面对“如何设计一个高并发缓存系统”这种问题时能自然地迁移出扩容、哈希冲突、负载因子这些思想。我自己的方法是给每个高频题准备一张“一页纸答案”格式固定为三块核心结论用两三句说清是什么推导过程讲清楚为什么这样做、解决了什么问题延伸场景结合项目说哪里用到过、踩过什么坑。这个方法让我在面试里逻辑清晰很多因为我的输出不是回忆背过的内容而是在现场重新推导一遍。还有一条心态建议遇到不会的问题不要慌不要直接说不会。面试官要的不是完美答案而是看你的思考路径。你可以说“这个点我确实没深入实践过不过根据我对XXX的理解它可能是这样工作的……”这种表达方式远好于沉默或者硬编。2023年的Java面试整体门槛确实比前几年高但高频考点其实就那么几大块沉下心把这套手册里的每个点都推导一遍面试现场会踏实很多。