深色模式
02 · 月度自测卷(30 题)
闭卷。不翻资料。限时 60 分钟。 目的不是打分,是找出"以为懂了其实没懂"的地方。
使用说明
① 先做题,把答案写在纸上(不要只在脑子里过,写出来才算)
② 做完再对答案,每题的"评分标准"告诉你答到哪算对
③ 统计错题,回到对应篇目重读
④ 一周后只重做错题评分标准(每题按"答到几层"给分):
0 分:答不出,或者完全说错
1 分:只答出"是什么"(第一层)
2 分:能答出"为什么这样设计"(第二层)
3 分:能答出"不这样会怎么坏"(第三层)第一部分 · 操作系统(1-8 题)
1. 进程和线程的本质区别是什么?为什么线程切换比进程切换便宜?
2. 画出进程的 5 个状态,并说明"阻塞能否直接转到运行",为什么?
3. 虚拟内存解决了哪三个具体问题?各举一个"不这么做会出的事故"。
4. 缺页中断的完整处理流程是什么?(至少 4 步)
5. 死锁的四个必要条件是什么?最实用的破坏手段是哪个?
6. 自旋锁和互斥锁分别适合什么场景?为什么单核 CPU 上不该用自旋锁?
7. 为什么说前四种 IO 模型都是"同步 IO"?
8. epoll 比 select 快在哪三点?什么情况下 epoll 不一定更快?
第二部分 · 计算机网络(9-16 题)
9. 三次握手的过程?为什么不是两次?(要举出一个具体会出事的场景)
10. 四次挥手为什么是四次?什么情况下会变成三次?
11. TIME_WAIT 为什么必须等 2MSL?两个理由分别是什么?
12. 流量控制和拥塞控制的区别?分别用什么实现?
13. 超时重传和快速重传的处理有什么不同?为什么要区别对待?
14. TCP 队头阻塞是什么?HTTP/2 为什么解决不了?HTTP/3 怎么解决的?
15. 强缓存和协商缓存的区别?no-cache 和 no-store 有什么不同?
16. HTTPS 为什么要"非对称 + 对称"混用?证书解决的是什么问题?
第三部分 · 数据结构与算法(17-23 题)
17. 数组和链表都是 O(n) 遍历,为什么实测差 10 倍?
18. 哈希表的 O(1) 是平均还是最坏?装太满会怎样?
19. 为什么数据库索引用 B+ 树,而不是哈希、红黑树、B 树?(四个都要说)
20. 堆为什么用数组存?建堆为什么是 O(n) 而不是 O(n log n)?
21. 为什么无权图的最短路用 BFS,而不是 DFS?
22. 并查集的两个优化是什么?为什么优化后接近 O(1)?
23. 快排最坏是 O(n²),什么时候会发生?怎么避免?
第四部分 · 数据库(24-30 题)
24. 2000 万行的表,B+ 树大概几层?一次查询几次磁盘 IO?(要能算出过程)
25. 什么是回表?什么是覆盖索引?
26. 联合索引 (a,b,c),WHERE a > 1 AND b = 2 能用到 b 吗?为什么?
27. ACID 分别由什么机制实现?为什么说"C 不是数据库实现的"?
28. redo log 和 binlog 的四个区别?为什么需要两阶段提交?
29. MVCC 靠哪三样东西实现?RC 和 RR 的唯一区别是什么?
30. 一条 SQL 很慢,你的排查顺序是什么?(至少 5 步)
答案与评分标准
先做完再看。
第一部分 · 操作系统
1. 进程和线程的区别
- 1 分:进程是资源分配单位,线程是调度单位;线程共享地址空间。
- 2 分:+ 线程独占栈和寄存器,共享堆和代码段。
- 3 分:+ 切换成本的差别在页表——进程切换要换 CR3、导致 TLB 失效,线程共享页表不用换。
- 命门:能不能说到 TLB。
2. 进程 5 状态
- 状态:新建、就绪、运行、阻塞、终止。
- 阻塞不能直接到运行:因为 CPU 可能被别的进程占着,事件到达只是"具备运行条件",还要重新排队等调度。
- 3 分:+ 能说清"运行>就绪"是被动(时间片),"运行>阻塞"是主动(发起 IO)。
- 命门:"阻塞 > 就绪 > 运行"这条路径。
3. 虚拟内存的三个价值
- ① 隔离:每个进程独立地址空间,不会互相踩。
- ② 超卖:虚拟总量可以超过物理内存,按需调页。
- ③ 免连续:物理页可以散落,不需要连续的大块内存。
- 3 分:每个都能举出"不这么做会出的事故"。
- 命门:三个都要说,只说"扩大内存"是 0 分。
4. 缺页中断流程
- ① 找空闲页框(没有就按置换算法选一个牺牲页)
- ② 牺牲页脏了先写回磁盘
- ③ 从磁盘/文件读入需要的页
- ④ 更新页表(存在位=1,填物理页号)
- ⑤ 重新执行触发缺页的那条指令
- 3 分:+ 能说清"哪些是硬件做的、哪些是内核做的"。
- 命门:第 ⑤ 步"重新执行"不能漏。
5. 死锁四条件
- 互斥、持有并等待、不可剥夺、循环等待。
- 最实用:破坏循环等待——所有资源按全局顺序申请。
- 3 分:+ 能解释"为什么按顺序申请就不会有环"。
- 命门:说清"破坏哪个"比背四条重要。
6. 自旋锁 vs 互斥锁
- 临界区极短 + 多核 > 自旋(省内核切换);可能阻塞 > 互斥。
- 单核不能用自旋:占着唯一的核空转,持有锁的线程没机会运行来释放锁。
- 3 分:+ 提到"读写锁适合读多写少"。
- 命门:"单核为什么不行"这半句。
7. 前四种都是同步 IO
- 因为第二阶段(内核缓冲区 > 用户缓冲区)都是阻塞的,都要自己等拷贝完成。
- 只有 AIO 第二阶段也不阻塞。
- 命门:判据是"第二阶段谁完成、完不完成通知你"。
8. epoll 快的三点
- ① 不用每次全量拷贝 fd 集合(注册一次)
- ② 不用遍历(回调把就绪的挂到链表)
- ③ 只返回就绪的 fd
- 不一定更快:fd 少且全活跃时,select 遍历开销小,epoll 的回调注册反而是额外成本。
- 命门:能说出"什么时候 epoll 不占优"。
第二部分 · 计算机网络
9. 三次握手
- SYN(seq=x) > SYN+ACK(seq=y, ack=x+1) > ACK(ack=y+1)。
- 为什么不是两次:如果只有两次,服务器收到一个延迟到达的历史 SYN 就会立刻认为连接建立、分配资源;而客户端根本不认这个连接,会回 RST 或忽略 > 服务器白占资源。三次握手的第三次让服务器确认"客户端真的收到了我的 SYN"。
- 3 分:+ 提到"第三次可以带数据,前两次不能"。
- 命门:历史 SYN 这个场景要讲完整。
10. 四次挥手
- 因为全双工,两个方向要独立关闭。服务器收到 FIN 时可能还有数据没发完,所以 ACK 和 FIN 不能合并。
- 变成三次:服务器确实没数据了,ACK+FIN 合并发送(延迟确认)。
- 命门:"还有数据要发"这个理由。
11. TIME_WAIT 两个理由
- ① 保证最后一个 ACK 能到达(丢了对方会重发 FIN,还能再回 ACK)
- ② 让旧连接的残留报文在网络中消亡,避免污染使用相同四元组的新连接
- 为什么 2MSL:覆盖"我的 ACK 到对方"+"对方的 FIN 到我"一个完整往返。
- 命门:两个理由都要说。
12. 流量控制 vs 拥塞控制
- 流量控制:端到端,接收方通告窗口,防止发送方淹没接收方。用滑动窗口。
- 拥塞控制:全局,发送方推测网络拥堵,防止压垮网络。用 cwnd。
- 实际窗口 = min(rwnd, cwnd)。
- 命门:"一个怕对方收不下,一个怕路上堵"。
13. 超时 vs 快速重传
- 超时:连 ACK 都没回来 > 判断严重拥塞 > cwnd 降到 1,重新慢启动。
- 快速重传:3 个重复 ACK > 个别丢包 > cwnd 减半,快速恢复。
- 为什么区别对待:一个是"路断了",一个是"掉了个包裹"。
- 命门:这个类比要能说出来。
14. TCP 队头阻塞
- TCP 保证按序交付,前面一段丢了,后面到的也要等。
- HTTP/2 的多路复用是在同一条 TCP 连接上做,TCP 层丢包会卡住所有流——反而更糟。
- HTTP/3 用 QUIC(UDP),每流独立排序和重传。
- 命门:区分"应用层 HOL"和"TCP 层 HOL"。
15. 强缓存 vs 协商缓存
- 强缓存:
Cache-Control: max-age,不发请求,直接用本地。 - 协商缓存:
If-None-Match/If-Modified-Since,要发请求,没变回 304(不带 body)。 - no-cache:可以缓存,但每次必须校验(不是"不缓存")。
- no-store:完全禁止缓存。
- 命门:no-cache 的陷阱。
16. HTTPS 混合加密
- 非对称(RSA/ECDHE)安全但慢,只用来协商会话密钥;对称(AES)快,用来传数据。
- 证书解决的问题:确认"这个公钥确实属于这个域名",靠 CA 签名 + 证书链逐级验证到浏览器内置的根证书。
- 3 分:+ 提到"用户信任了恶意根证书时 HTTPS 也防不住"。
- 命门:证书解决的是"公钥归属"。
第三部分 · 数据结构与算法
17. 数组比链表快 10 倍
- CPU 按缓存行(64 字节)读内存。数组连续,读一个元素会把邻居一起载入缓存 > 后续访问命中缓存。
- 链表节点散落,每次
next大概率缓存未命中,要去主存取(约 100 周期)。 - 3 分:+ 提到"硬件预取器能识别顺序访问,对随机跳转无能为力"。
- 命门:缓存行 + 预取器。
18. 哈希表的 O(1)
- 平均 O(1),最坏 O(n)(全部冲突)。
- 装太满 > 冲突概率上升 > 链表变长 > 查找退化。
- 解法:负载因子(0.75)+ 扩容(2 倍)+ 链表转红黑树(Java 8,≥8 个)。
- 命门:"平均"这两个字。
19. 为什么 B+ 树(四问四答)
- vs 哈希:不支持范围查询、排序、最左前缀。
- vs 红黑树:树高 24 层 = 24 次磁盘 IO(每次 10ms)。
- vs B 树:B 树数据存在所有节点,非叶子能存的键变少,树高更高;且范围查询要在树上反复上下跳。
- B+ 树的优势:数据全在叶子(非叶子能存更多键 > 树高更低)、叶子有链表(范围查询顺序扫)、每次查询都到叶子(IO 次数稳定)。
- 命门:所有比较都落到"磁盘 IO 次数 = 树高"。
20. 堆
- 为什么用数组:完全二叉树可以用下标算父子(
2i+1、2i+2),不需要指针,缓存友好。 - 建堆 O(n):从最后一个非叶子节点往前下沉。大部分节点在底层、下沉距离短,总代价是
n/2×0 + n/4×1 + n/8×2 + ...收敛到 O(n)。 - 对比"插入 n 次"是 O(n log n),因为每个都从底部上浮全程。
- 命门:"越往下节点越多但下沉越少"。
21. BFS 找无权最短路
- BFS 按层扩展,第一次到达目标时边数一定最少(所有更短的都在更早的层)。
- DFS 是"一条路走到黑",可能先返回一条长路径。
- 前提:无权(或边权相同)。有权重必须用 Dijkstra。
- 命门:那个"前提"。
22. 并查集
- 两个优化:路径压缩(查找时把路径上所有节点直接挂到根)+ 按秩合并(矮树挂到高树下)。
- 复杂度 O(α(n)),α 是反阿克曼函数,实际 ≤ 4,可视为 O(1)。
- 命门:两个优化都要说,只说一个只能得 1 分。
23. 快排最坏 O(n²)
- 每次 pivot 都是极值,分区极不均衡,递归深度 n。
- 什么时候:数组已排序/逆序 + 固定选首/尾元素当 pivot。
- 避免:随机 pivot、三数取中、三路快排、小区间用插入排序。
- 命门:"已排序的数组最慢"这个反直觉结论。
第四部分 · 数据库
24. B+ 树层数
- 非叶子:键 8B + 指针 6B = 14B,一页 16KB > 约 1170 个键。
- 叶子:一行约 1KB > 一页约 16 行。
- 三层:
1170 × 1170 × 16 ≈ 2200 万行。 - 所以 2000 万行 = 3 层,一次查询最多 3 次 IO。
- 命门:能写出 1170 这个数是怎么来的。
25. 回表 / 覆盖索引
- 回表:二级索引叶子只存主键,查到主键后要再去聚簇索引查整行 > 两次 B+ 树查找。
- 覆盖索引:查询的列全在索引里,不用回表。
- 例:索引
(name, age),SELECT name, age WHERE name='张三'就覆盖了。 - 命门:能写出一个具体的索引 + SQL 组合。
26. 最左前缀
- 不能。
- 因为索引按 (a,b,c) 排序,
a > 1会命中多个 a 值,b 在每个 a 内部有序但跨 a 整体无序,无法用于定位。 - b 只能用于"索引下推"过滤(减少回表),不能缩小扫描范围。
- 命门:"跨 a 整体无序"这个解释。
27. ACID 实现
- A > undo log(记录旧值,反向操作回滚)
- D > redo log(WAL,先写日志再刷数据页)
- I > 锁 + MVCC
- C 不是数据库实现的:一致性是业务目标(余额不能为负、总额不变),数据库只提供 A/I/D 这些手段,C 需要应用逻辑 + 约束共同保证。
- 命门:"C 是目标,AID 是手段"。
28. redo vs binlog + 两阶段提交
- 四个区别:层级(引擎层 vs Server 层)、内容(物理 vs 逻辑)、写入(循环写 vs 追加写)、用途(崩溃恢复 vs 主从复制/备份)。
- 为什么要两阶段提交:一次提交要写两个日志,必须保证原子性。先写 redo(prepare)> 写 binlog > redo(commit)。崩溃时看 binlog 是否完整 决定提交还是回滚。
- 命门:"用 binlog 的完整性决定 redo 的去留"。
29. MVCC
- 三件套:隐藏字段(
DB_TRX_ID+DB_ROLL_PTR)、undo log 版本链、ReadView。 - RC 和 RR 的唯一区别:ReadView 的生成时机。RC 每次 SELECT 都生成,RR 只在第一次 SELECT 时生成并复用。
- 补充:MVCC 只能解决快照读的幻读,当前读要靠间隙锁。
- 命门:"只差一处:ReadView 生成时机"。
30. 慢 SQL 排查
- ① 确认真的慢(慢查询日志)
- ② EXPLAIN 看 type / key / rows / Extra
- ③ 判断"没索引"还是"索引失效"
- ④ 看是否返回太多数据(SELECT * / 深分页)
- ⑤ 看是否锁等待(长事务)
- ⑥ 看数据量(分表 / 归档)
- ⑦ 看配置(Buffer Pool、连接数、硬件)
- 命门:不能一上来就说"加索引"。
自评表
计分
满分:30 题 × 3 分 = 90 分
75-90:骨架扎实,可以开始第 2 个月(语言/框架/中间件)
60-74:主干有了,把错题所在篇目重读一遍,两周后重考
40-59:概念记住了但"为什么"没通,重点补每篇的第 4 节(为什么这么设计)
< 40 :回到导读篇,重新建立模块地图,别急着背细节错题记录
| 题号 | 得分 | 属于哪个模块 | 错在哪一层 | 要重读的篇目 |
|---|---|---|---|---|
"错在哪一层"怎么填:
- 第 1 层(是什么)错 > 没记住,重读一遍即可
- 第 2 层(为什么这样设计)错 > 理解不够,重点看"为什么这么设计"和"反事实"
- 第 3 层(不这样会怎么坏)错 > 没真懂,回去看"类比失效处"和追问链
按模块统计
| 模块 | 得分 | 满分 | 薄弱点 |
|---|---|---|---|
| 操作系统 | 24 | ||
| 计算机网络 | 24 | ||
| 数据结构 | 21 | ||
| 数据库 | 21 | ||
| 合计 | 90 |
下一步
得分最低的那个模块,就是你第 2 个月要补的第一块。
但更重要的判断是:错在第 2 层还是第 3 层?
第 1 层错得多 > 你只是"忘了",重读就行,不用焦虑
第 2/3 层错得多 > 说明当初学的时候就没建立因果,需要重新走一遍"反事实"
真正危险的是:答对了第 1 层,就以为自己会了。✅ 验收清单
- 闭卷、限时 60 分钟,独立完成 30 题(不翻资料、不查手机)
- 按三层标准给自己打分,而不是"大概会"
- 填完错题记录表,每道错题都能指出"错在哪一层"
- 统计出最薄弱的模块,并列出要重读的 3 篇
- 一周后只重做错题,正确率到 80% 以上
- 能说出"这次自测暴露的最大问题是什么"(一句话)