⏱️ 考前速查(面试前 3 天看这一页)
14 个模块 69 条一句话题解 · 面试前 3 天的自检清单
🗺️ 怎么用这一页?
这一页是面试前 3 天的复习清单,不是教程。每条都是「问题 → 一句话答案」,用来快速自检:扫一眼能答上来就跳过,答不上来的点链接回原页补。
🎯 使用建议
- 面试前 3 天:通读一遍,标记答不上来的条目
- 面试前 1 天:只看标记的条目 + 各模块的「名词解释」表
- 面试当天早上:只看「算法」和「设计模式」两节,以及各模块的一句话结论
- 页面顶部的「📖 自测模式」可以把答案折起来,先自己想再看
☕ Java 基础(8 条)
- String 为什么不可变?——final class + private final char[](JDK9+ 是 byte[])+ 不暴露修改入口。好处:线程安全、可缓存 hash、常量池复用、安全(防篡改路径/类名)
- String s = new String("abc") 创建几个对象?——2 个(常量池 1 个 + 堆 1 个),若池中已有 "abc" 则只新建 1 个堆对象
- == 和 equals?——== 比引用(基本类型比值),equals 默认也是比引用,String/包装类重写成了比值。所以字符串比较永远用 equals
- StringBuilder 和 StringBuffer?——后者方法加了 synchronized,现代代码几乎只用 StringBuilder(局部变量天然线程私有)
- ArrayList 和 LinkedList?——ArrayList 随机访问 O(1)、尾部增删均摊 O(1);LinkedList 任意位置插入删除 O(1) 但要先 O(n) 找到节点,且每个元素多两个指针。99% 场景用 ArrayList
- HashMap 为什么容量是 2 的幂?——这样
(n-1) & hash等价于 hash % n 但更快;扩容时元素要么留在原位、要么移动 oldCap,无需重新计算 hash - HashMap 扩容过程?——默认容量 16、负载因子 0.75,元素数超过阈值就翻倍;JDK8 用高低位拆分(
(e.hash & oldCap) == 0留原位);链表长度 ≥ 8 且容量 ≥ 64 转红黑树,退化阈值是 6 - HashMap 为什么线程不安全?——并发 put 可能覆盖、JDK7 头插还会成环。并发场景用 ConcurrentHashMap
🔗 并发编程(8 条)
- 线程安全的三个必要条件?——原子性、可见性、有序性。不是「破坏它的条件」,是必须同时满足的三个条件
- volatile 的作用?——保证可见性 + 禁止指令重排(内存屏障),不保证原子性(
i++照样错)。适合状态标志位、DCL 单例 - synchronized 锁升级?——无锁 → 偏向锁 → 轻量级锁(CAS + 自旋)→ 重量级锁(OS 互斥量)。锁信息在对象头 Mark Word;JDK 15 起偏向锁默认关闭;重入次数不在 Mark Word 里(偏向锁靠线程 ID、轻量级靠多个 Lock Record、重量级靠 ObjectMonitor._recursions)
- happens-before 是什么?——JMM 用来判断可见性的规则集(程序顺序、锁、volatile、传递性、start/join)。注意:可见性仍由同步动作提供,没有同步点就没有跨线程的 happens-before 边
- 线程池七个参数?——corePoolSize、maxPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler(拒绝策略)
- 线程池执行流程?——核心线程没满就建核心线程 → 队列没满就入队 → 没满最大线程就建临时线程 → 都满了走拒绝策略
- 为什么不建议用 Executors?——
newFixedThreadPool/newSingleThreadExecutor用无界队列会 OOM;newCachedThreadPool最大线程数是 Integer.MAX_VALUE 会创建过多线程。手写 ThreadPoolExecutor - 死锁四个条件?——互斥、持有并等待、不可剥夺、循环等待。破坏循环等待最实用(按固定顺序加锁)
⚙️ JVM(6 条)
- 运行时数据区?——程序计数器(唯一不会 OOM)、虚拟机栈、本地方法栈(线程私有);堆、方法区/元空间(线程共享)
- 对象创建过程?——类加载检查 → 分配内存(指针碰撞/空闲列表,TLAB 避免竞争)→ 初始化零值 → 设置对象头 → 执行构造方法
- 怎么判断对象可回收?——可达性分析(从 GC Roots 出发),不是引用计数(解决不了循环引用)
- 四种引用?——强(默认)、软(内存不足才回收,做缓存)、弱(下次 GC 必回收,ThreadLocal 的 key)、虚(只用于回收通知,DirectByteBuffer 的 Cleaner)
- G1 的特点?——堆分成 Region,可预测停顿(MaxGCPauseMillis),整体标记-整理 + 局部复制,JDK 9+ 默认;JDK 10 起 Full GC 已并行化
- 常用 JVM 调优参数?——
-Xms/-Xmx(设成相等)、-Xmn、-XX:MetaspaceSize、-XX:+HeapDumpOnOutOfMemoryError、GC 日志用 JDK 9+ 的-Xlog:gc*(旧的 PrintGCDetails 在 JDK 14 已移除)
🍃 Spring(7 条)
- IoC 是什么?——对象的创建和依赖装配交给容器,而不是自己 new。好处:解耦、便于替换与测试
- BeanFactory 和 FactoryBean 的区别?——
BeanFactory是 IoC 容器本身(按 BeanDefinition 创建 Bean);FactoryBean是一种特殊的 Bean,它的getObject()返回的才是真正要用的对象(MyBatis 的 Mapper 代理就是它生产的)。两个不同接口,别混 - Bean 生命周期?——实例化 → 属性填充 → Aware 回调 → BeanPostProcessor 前置 → @PostConstruct/afterPropertiesSet → BPP 后置(AOP 代理在这里生成)→ 使用 → 销毁
- 循环依赖怎么解决?——三级缓存:成品 Map、半成品 Map、ObjectFactory Map。三级存工厂是为了延迟决定要不要提前生成 AOP 代理;构造器注入的循环无法解决(直接抛 BeanCurrentlyInCreationException);Spring Boot 2.6+ 默认禁止循环依赖
- AOP 的代理选择?——目标类有接口用 JDK 动态代理,没有接口用 CGLIB;Spring Boot 2.x 起默认强制 CGLIB(proxy-target-class=true)
- @Transactional 什么时候失效?——方法自调用(不走代理)、非 public、异常被 catch 吞掉、抛受检异常(默认只回滚 RuntimeException/Error)、类没被 Spring 管理、final 类/方法、多线程调用。口诀:AOP 失效的它都失效
- 自动配置原理?——启动类 @SpringBootApplication(含 @EnableAutoConfiguration)→ @Import 导入 AutoConfiguration.imports 里的候选类 → 按 @Conditional 系列条件筛选 → 注册 Bean
🗄️ MySQL(8 条)
- 为什么用 B+ 树?——非叶子只存键 → 树更矮(3 层约 1600 万行)→ IO 少;叶子双向链表 → 范围查询直接顺序扫描;查询性能稳定
- 聚簇索引和二级索引?——聚簇索引(主键)叶子存整行;二级索引叶子存「索引列 + 主键」;查非索引列要回表。查询列全在索引里叫覆盖索引,不用回表
- 最左前缀原则?——联合索引 (a,b,c):a、a+b、a+b+c 能用于定位;范围查询之后的列不能缩小扫描区间,但 MySQL 5.6+ 的 ICP 能在引擎层用它过滤、减少回表
- 索引失效的常见情况?——函数/运算作用在索引列上、隐式类型转换、
like '%x'、or连接非索引列、!=/is not null有时也会 - MVCC 怎么实现的?——隐藏列 trx_id + roll_pointer、undo log 版本链、ReadView (m_ids / min_trx_id / max_trx_id / creator_trx_id)。RC 每次 SELECT 建新 ReadView,RR 只建一次
- 四种隔离级别?——读未提交、读已提交、可重复读(MySQL 默认)、串行化。InnoDB 的 RR 靠 MVCC(快照读)+ 临键锁(当前读)基本解决了幻读
- 间隙锁什么时候加?——只在 RR 及以上;RC 下搜索与索引扫描不加,但外键检查和唯一键重复检查仍会加
- redo log 和 binlog 的区别?——redo 是 InnoDB 引擎层、物理日志、循环写;binlog 是 Server 层、逻辑日志、追加写。两阶段提交保证二者一致(8.0 没有取消 2PC,只是优化了组提交)
🔴 Redis(6 条)
- 为什么快?——纯内存、单线程免锁与上下文切换、IO 多路复用(epoll)、高效数据结构。「单线程」指命令执行单线程;Redis 4.0 起有异步删除线程,6.0 起有 io-threads 多线程网络 IO
- 缓存穿透 / 击穿 / 雪崩?——穿透:查不存在的 key(布隆过滤器 / 缓存空值);击穿:单个热点 key 过期瞬间(互斥锁回填 / 逻辑过期);雪崩:大量 key 同时过期或缓存层挂(TTL 随机化 / 多级缓存 / 限流降级)
- 缓存和数据库一致性?——Cache Aside:先更新 DB,再删缓存(删除天然幂等,并发窗口最小)。兜底:删缓存失败重试、延迟双删、canal 订阅 binlog 异步删。强一致需求就别用缓存
- 持久化方式?——RDB(快照,恢复快、可能丢数据)+ AOF(命令日志,everysec 最多丢 1 秒)+ 混合持久化(4.0+,推荐)
- 分布式锁怎么实现?——
SET key value NX EX(value 用唯一 ID),释放用 Lua 校验后删(防误删)。生产用 Redisson;集群下 RedLock 有争议(Kleppmann vs antirez),真正严谨的方案是 fencing token - 过期删除和内存淘汰?——过期:惰性删除(访问时查)+ 定期删除(每秒 10 次抽样);淘汰:8 种策略,默认 noeviction,纯缓存用 allkeys-lru
🌍 计算机网络(5 条)
- TCP 三次握手 / 四次挥手?——握手 SYN → SYN+ACK → ACK;挥手 FIN → ACK → FIN → ACK。为什么 4 次:TCP 全双工,两个方向要分别关闭
- 为什么需要 TIME_WAIT?——① 保证最后一个 ACK 能到达(否则对方重发 FIN);② 让本连接的旧报文在网络中消散,避免影响新连接。等 2MSL
- CLOSE_WAIT 很多说明什么?——代码问题:收到对方 FIN 后没有调用 close()。TIME_WAIT 多才是正常的(主动关闭方)
- HTTP/1.1、2、3 的演进?——1.1 keep-alive + 管线化(仍有队头阻塞);2.0 二进制分帧 + 多路复用 + HPACK(解决应用层队头阻塞,TCP 层仍阻塞);3.0 基于 QUIC/UDP(解决 TCP 层队头阻塞,支持连接迁移)
- HTTPS 握手?——TLS 1.2:ClientHello(随机数 A)→ ServerHello(随机数 B)+ 证书 → 客户端验证证书、生成 pre-master secret 用服务端公钥加密发送 → 双方用「pre-master + 两个随机数」导出会话密钥 → 之后对称加密传输。注意只有两个随机数,不是三个;实践用 ECDHE 才有前向安全
💻 操作系统(5 条)
- 进程和线程的区别?——进程是资源分配单位(独立地址空间);线程是 CPU 调度单位(共享地址空间)。线程切换比进程切换便宜(不用换页表),但仍要切换内核栈、寄存器和刷新缓存
- 线程切换保存什么?——程序计数器、寄存器、栈指针等线程私有上下文;页表属于进程切换,不是线程切换
- 零拷贝是什么?——普通 IO 4 次拷贝(2 次 DMA + 2 次 CPU);mmap + write 省掉内核→用户的拷贝;sendfile 让数据不进用户态,Kafka 消费、Netty 文件传输用它
- IO 多路复用?——select(fd 位图,上限 1024)、poll(数组,无上限)、epoll(红黑树 + 就绪链表,只返回就绪的 fd)。epoll 支持 LT/ET
- 死锁四个条件?——互斥、持有并等待、不可剥夺、循环等待。银行家算法属于死锁「避免」,不是「检测」;检测靠资源分配图找环
✉️ 消息队列(4 条)
- MQ 的三大作用和代价?——作用:异步、解耦、削峰;代价:可用性下降、系统复杂度上升、一致性问题(要处理重复和丢失)
- 怎么保证消息不丢?——生产端:
acks=all+ 重试(Kafka 3.0 起 acks 默认就是 all);Broker:副本 + 刷盘;消费端:处理完业务再提交 offset(先提交后处理会丢) - 怎么保证不重复消费?——MQ 一般只保证 at-least-once,所以要在消费端做幂等:唯一索引、SETNX、状态机、乐观锁。用业务键幂等,比用消息 ID 更可靠
- 怎么保证顺序?——同一业务 key 路由到同一分区/队列 + 单消费者串行消费。注意:消费失败重试会打乱顺序
🌐 分布式(5 条)
- CAP 是什么?——一致性、可用性、分区容错性。不是「平时三选二」,而是「发生分区时在 C 和 A 之间选」;P 必须接受
- BASE 是什么?——基本可用、软状态、最终一致。是对 CAP 中 AP 的工程化延伸
- 分布式事务方案?——2PC/3PC(强一致、阻塞)、TCC(Try-Confirm-Cancel,要处理空回滚/悬挂/幂等)、Saga(长事务拆本地事务 + 逆序补偿)、本地消息表、RocketMQ 事务消息、Seata(AT/TCC/Saga/XA)
- 分布式锁要注意什么?——加锁要带超时、value 唯一、释放要校验、要有 fencing token 防「GC 暂停后误以为还持锁」
- 分布式 ID 方案?——雪花算法(1 符号 + 41 时间戳 + 5 数据中心 + 5 机器 + 12 序列,单位毫秒 4096 个,要处理时钟回拨)、号段模式(Leaf-segment 双 buffer)、Redis INCR、UUID(不趋势递增,不适合做 MySQL 主键)
🧮 算法(4 条)
- 常见排序的复杂度?——快排平均 O(n log n)/最坏 O(n²)/不稳定/空间 O(log n);归并 O(n log n)/稳定/空间 O(n);堆排 O(n log n)/不稳定/空间 O(1);冒泡插入稳定,选择不稳定。桶排序最坏 O(n²),计数排序空间 O(n+k)
- Arrays.sort 用什么?——基本类型双轴快排,对象类型 TimSort(稳定)
- TopK 怎么做?——① 小顶堆 O(n log k)(内存只占 k,适合海量数据);② 快排 partition 剪枝平均 O(n);③ 全排序 O(n log n) 最差
- 手撕高频题?——LRU(HashMap + 双向链表)、单例(DCL + volatile)、生产者消费者(wait/notify 或 Condition)、快排(挖坑法 partition)、反转链表、两数之和、接雨水、岛屿数量
📐 设计模式(3 条)
- 最常考哪几个?——单例、工厂、代理、模板方法、观察者、策略、责任链、装饰器
- Spring 里用了哪些?——工厂(BeanFactory)、代理(AOP)、模板方法(refresh()、JdbcTemplate)、观察者(事件)、适配器(HandlerAdapter)、策略(HandlerMapping)。注意 FactoryBean 不是「工厂模式」的 BeanFactory
- 手写单例六种写法?——饿汉、懒汉(加锁)、DCL(必须 volatile)、静态内部类、枚举(天然防反射和序列化破坏,推荐)、双重检查的变体