第二阶段:计算机基础强化(8.11 - 8.24,2周)
学习目标
夯实数据结构与算法基础,掌握操作系统核心概念,能够应对字节跳动面试中的算法手撕题和计算机基础八股文。目标:LeetCode 中等难度稳定 AC,操作系统高频考点能完整口述。
模块一:数据结构(Day 1-3)
1.1 数组与链表
必须掌握的知识点:
- 数组:连续内存、O(1) 随机访问、O(n) 插入删除
- 链表:非连续内存、O(n) 访问、O(1) 插入删除(已知位置)
- 单链表、双链表、循环链表
- 哨兵节点(dummy head)简化边界处理
- Go 中 slice 底层就是数组;链表需手动实现
高频面试题(必刷):
| 题目 | LeetCode | 难度 | 考点 |
|---|---|---|---|
| 反转链表 | 206 | Easy | 指针操作 |
| 合并两个有序链表 | 21 | Easy | 双指针 |
| 环形链表 II | 142 | Medium | 快慢指针 |
| 删除链表倒数第N个节点 | 19 | Medium | 快慢指针 |
| 两数相加 | 2 | Medium | 链表遍历 |
| 合并K个升序链表 | 23 | Hard | 分治/堆 |
| LRU 缓存 | 146 | Medium | 双向链表+哈希表 |
八股文要点:
- "数组和链表的区别?各自适用场景?"
- "如何检测链表有环?如何找到环的入口?" → Floyd 判圈法
- "如何在 O(1) 时间删除链表节点?" → 值覆盖法
1.2 栈与队列
必须掌握的知识点:
- 栈:LIFO,Go 用 slice 模拟(append + 截取)
- 队列:FIFO,Go 用 slice 或 container/list
- 单调栈:维护单调递增/递减序列,解决"下一个更大元素"类问题
- 单调队列:滑动窗口最大值
- 优先队列(堆):Go 的
container/heap接口
高频面试题:
| 题目 | LeetCode | 难度 | 考点 |
|---|---|---|---|
| 有效的括号 | 20 | Easy | 栈 |
| 最小栈 | 155 | Medium | 辅助栈 |
| 每日温度 | 739 | Medium | 单调栈 |
| 滑动窗口最大值 | 239 | Hard | 单调队列 |
| 前K个高频元素 | 347 | Medium | 堆 |
| 用栈实现队列 | 232 | Easy | 双栈 |
八股文要点:
- "栈和队列的区别?各自的典型应用场景?"
- "如何用两个栈实现队列?时间复杂度?" → 均摊 O(1)
- "单调栈解决什么类型的问题?时间复杂度?"
1.3 哈希表
必须掌握的知识点:
- 哈希函数:将 key 映射到数组下标
- 冲突解决:
- 链地址法(Go map 使用)
- 开放寻址法(线性探测、二次探测)
- 负载因子:元素数/桶数,Go map 的负载因子阈值 6.5
- 扩容:翻倍 + rehash(Go 是渐进式迁移)
- Go map 不可并发读写(第一阶段已学)
高频面试题:
| 题目 | LeetCode | 难度 | 考点 |
|---|---|---|---|
| 两数之和 | 1 | Easy | 哈希查找 |
| 字母异位词分组 | 49 | Medium | 哈希分组 |
| 最长连续序列 | 128 | Medium | 哈希集合 |
| 无重复字符的最长子串 | 3 | Medium | 哈希+滑窗 |
八股文要点:
- "哈希冲突的解决办法有哪些?各自优缺点?"
- "HashMap 的扩容机制?为什么是 2 的幂次?" → 位运算取模
- "一致性哈希是什么?解决什么问题?" → 分布式场景
1.4 树
必须掌握的知识点:
- 二叉树遍历:前序、中序、后序、层序(递归 + 迭代)
- BST(二叉搜索树):中序有序、查找/插入/删除 O(logn)~O(n)
- 平衡树概念:AVL、红黑树(面试只需知道性质和应用场景)
- 堆:完全二叉树、最大堆/最小堆、上浮/下沉操作
- 前缀树(Trie):字符串前缀匹配
高频面试题:
| 题目 | LeetCode | 难度 | 考点 |
|---|---|---|---|
| 二叉树的层序遍历 | 102 | Medium | BFS |
| 翻转二叉树 | 226 | Easy | 递归 |
| 二叉树的最大深度 | 104 | Easy | DFS |
| 验证二叉搜索树 | 98 | Medium | 中序遍历 |
| 二叉树的最近公共祖先 | 236 | Medium | 递归 |
| 二叉树的右视图 | 199 | Medium | BFS |
| 前K个高频元素 | 347 | Medium | 堆 |
| 实现 Trie | 208 | Medium | 前缀树 |
八股文要点:
- "红黑树的五个性质?为什么 Go 没有在 map 中用红黑树?" → 链地址法 + 溢出桶更适合 Go 的设计
- "堆排序的时间复杂度?为什么不如快排常用?" → 缓存不友好
- "B 树和 B+ 树的区别?为什么数据库用 B+ 树?" → 叶子节点链表,范围查询高效
1.5 图
必须掌握的知识点:
- 表示方法:邻接矩阵、邻接表
- BFS:层序遍历、最短路径(无权图)
- DFS:连通性、拓扑排序、回溯
- 拓扑排序:入度法(Kahn)/ DFS 后序反转
- 最短路径:Dijkstra(单源)、Floyd(多源)— 了解即可
- 并查集:连通分量、判断是否有环
高频面试题:
| 题目 | LeetCode | 难度 | 考点 |
|---|---|---|---|
| 岛屿数量 | 200 | Medium | DFS/BFS |
| 课程表 | 207 | Medium | 拓扑排序 |
| 课程表 II | 210 | Medium | 拓扑排序 |
| 腐烂的橘子 | 994 | Medium | 多源BFS |
| 克隆图 | 133 | Medium | DFS+哈希 |
八股文要点:
- "DFS 和 BFS 的区别?各自适用什么场景?"
- "拓扑排序用来解决什么问题?时间复杂度?"
- "如何检测图中是否有环?有向 vs 无向?"
模块二:算法(Day 4-9)
2.1 排序算法
必须掌握的知识点:
| 算法 | 时间(平均) | 时间(最坏) | 空间 | 稳定性 |
|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 |
| 快速排序 | O(nlogn) | O(n²) | O(logn) | 不稳定 |
| 归并排序 | O(nlogn) | O(nlogn) | O(n) | 稳定 |
| 堆排序 | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
必须手写:快速排序、归并排序
go
// 快速排序 - 必须能15分钟内手写
func QuickSort(arr []int, left, right int) {
if left >= right {
return
}
pivot := partition(arr, left, right)
QuickSort(arr, left, pivot-1)
QuickSort(arr, pivot+1, right)
}
func partition(arr []int, left, right int) int {
pivot := arr[right]
i := left
for j := left; j < right; j++ {
if arr[j] < pivot {
arr[i], arr[j] = arr[j], arr[i]
i++
}
}
arr[i], arr[right] = arr[right], arr[i]
return i
}
// 归并排序
func MergeSort(arr []int) []int {
if len(arr) <= 1 {
return arr
}
mid := len(arr) / 2
left := MergeSort(arr[:mid])
right := MergeSort(arr[mid:])
return merge(left, right)
}
func merge(left, right []int) []int {
result := make([]int, 0, len(left)+len(right))
i, j := 0, 0
for i < len(left) && j < len(right) {
if left[i] <= right[j] {
result = append(result, left[i])
i++
} else {
result = append(result, right[j])
j++
}
}
result = append(result, left[i:]...)
result = append(result, right[j:]...)
return result
}八股文要点:
- "快排为什么平均是 O(nlogn),最坏是 O(n²)?如何优化?" → 随机 pivot / 三数取中
- "快排和归并的区别?" → 快排原地、不稳定;归并稳定、需额外空间
- "什么是排序稳定性?什么场景需要稳定排序?"
- "Go 的 sort.Sort 用什么算法?" → pdqsort(Go 1.19+),混合排序
2.2 二分查找
必须掌握的知识点:
- 基本模板:左闭右闭
[left, right] - 变体:
- 查找第一个等于 target 的位置(lower_bound)
- 查找最后一个等于 target 的位置(upper_bound - 1)
- 查找第一个大于等于 target 的位置
- 关键:循环不变量,明确搜索区间
go
// 标准二分 - 左闭右闭
func BinarySearch(nums []int, target int) int {
left, right := 0, len(nums)-1
for left <= right {
mid := left + (right-left)/2
if nums[mid] == target {
return mid
} else if nums[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return -1
}
// 查找左边界
func LowerBound(nums []int, target int) int {
left, right := 0, len(nums)-1
for left <= right {
mid := left + (right-left)/2
if nums[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return left
}高频面试题:
| 题目 | LeetCode | 难度 | 考点 |
|---|---|---|---|
| 搜索旋转排序数组 | 33 | Medium | 二分变体 |
| 在排序数组中查找元素的第一个和最后一个位置 | 34 | Medium | 左右边界 |
| 搜索插入位置 | 35 | Easy | 基本二分 |
| 寻找峰值 | 162 | Medium | 二分 |
| 搜索二维矩阵 | 240 | Medium | 二分思想 |
2.3 双指针与滑动窗口
必须掌握的知识点:
- 对撞指针:两端向中间(有序数组两数之和、回文判断)
- 快慢指针:链表环检测、链表中间节点
- 滑动窗口:
- 固定窗口:窗口大小不变
- 可变窗口:满足条件时收缩
- 模板:右指针扩张 → 条件满足 → 左指针收缩
go
// 滑动窗口模板
func SlidingWindow(s string) int {
window := make(map[byte]int)
left := 0
result := 0
for right := 0; right < len(s); right++ {
c := s[right]
window[c]++
// 条件不满足时收缩
for /* 窗口需要收缩的条件 */ {
d := s[left]
window[d]--
left++
}
// 更新结果
result = max(result, right-left+1)
}
return result
}高频面试题:
| 题目 | LeetCode | 难度 | 考点 |
|---|---|---|---|
| 三数之和 | 15 | Medium | 排序+双指针 |
| 盛最多水的容器 | 11 | Medium | 对撞指针 |
| 无重复字符的最长子串 | 3 | Medium | 滑窗 |
| 最小覆盖子串 | 76 | Hard | 滑窗 |
| 找到字符串中所有字母异位词 | 438 | Medium | 固定滑窗 |
| 长度最小的子数组 | 209 | Medium | 滑窗 |
2.4 递归与回溯
必须掌握的知识点:
- 递归三要素:终止条件、递归逻辑、返回值
- 回溯模板:选择 → 递归 → 撤销选择
- 剪枝:提前终止无效搜索路径
- 常见应用:排列、组合、子集、N皇后
go
// 回溯模板
func backtrack(path []int, choices []int, result *[][]int) {
if /* 满足结束条件 */ {
tmp := make([]int, len(path))
copy(tmp, path)
*result = append(*result, tmp)
return
}
for i, choice := range choices {
// 剪枝
if /* 不满足约束 */ {
continue
}
path = append(path, choice) // 做选择
backtrack(path, choices[i+1:], result) // 递归
path = path[:len(path)-1] // 撤销选择
}
}高频面试题:
| 题目 | LeetCode | 难度 | 考点 |
|---|---|---|---|
| 全排列 | 46 | Medium | 回溯 |
| 组合总和 | 39 | Medium | 回溯+剪枝 |
| 子集 | 78 | Medium | 回溯 |
| 电话号码的字母组合 | 17 | Medium | 回溯 |
| 括号生成 | 22 | Medium | 回溯+剪枝 |
| N 皇后 | 51 | Hard | 回溯 |
2.5 动态规划
必须掌握的知识点:
- DP 五步法:
- 定义状态(dp[i] 代表什么)
- 状态转移方程
- 初始化
- 遍历顺序
- 返回值
- 常见类型:
- 线性 DP:爬楼梯、打家劫舍
- 背包问题:0-1 背包、完全背包
- 区间 DP:最长回文子串
- 序列 DP:最长递增子序列、最长公共子序列
- 二维 DP:路径问题
高频面试题:
| 题目 | LeetCode | 难度 | 考点 |
|---|---|---|---|
| 爬楼梯 | 70 | Easy | 基础DP |
| 最长递增子序列 | 300 | Medium | 序列DP |
| 零钱兑换 | 322 | Medium | 完全背包 |
| 最长公共子序列 | 1143 | Medium | 二维DP |
| 编辑距离 | 72 | Medium | 二维DP |
| 打家劫舍 | 198 | Medium | 线性DP |
| 不同路径 | 62 | Medium | 二维DP |
| 单词拆分 | 139 | Medium | DP+哈希 |
| 最长回文子串 | 5 | Medium | 区间DP |
八股文要点:
- "动态规划和贪心的区别?" → DP 枚举所有子问题取最优,贪心只看局部最优
- "如何判断一个问题能不能用 DP?" → 最优子结构 + 重叠子问题
- "背包问题的空间优化?" → 滚动数组 / 一维压缩
2.6 贪心算法
必须掌握的知识点:
- 核心思想:每步选择局部最优,期望全局最优
- 适用条件:贪心选择性质 + 最优子结构
- 证明方法:交换论证法(面试不要求严格证明)
高频面试题:
| 题目 | LeetCode | 难度 | 考点 |
|---|---|---|---|
| 跳跃游戏 | 55 | Medium | 贪心 |
| 跳跃游戏 II | 45 | Medium | 贪心 |
| 无重叠区间 | 435 | Medium | 区间贪心 |
| 合并区间 | 56 | Medium | 排序+贪心 |
| 分发糖果 | 135 | Hard | 两次遍历贪心 |
2.7 BFS/DFS 综合
必须掌握的知识点:
- BFS 模板(层序遍历):队列 + visited
- DFS 模板:递归 / 显式栈
- 矩阵搜索:四方向移动
dx = [0,0,1,-1] - 多源 BFS:多个起点同时入队
go
// BFS 模板(图/矩阵搜索)
func bfs(grid [][]int, startX, startY int) {
rows, cols := len(grid), len(grid[0])
visited := make([][]bool, rows)
for i := range visited {
visited[i] = make([]bool, cols)
}
queue := [][]int{{startX, startY}}
visited[startX][startY] = true
dirs := [][]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}}
for len(queue) > 0 {
cur := queue[0]
queue = queue[1:]
for _, d := range dirs {
nx, ny := cur[0]+d[0], cur[1]+d[1]
if nx >= 0 && nx < rows && ny >= 0 && ny < cols &&
!visited[nx][ny] && grid[nx][ny] == 1 {
visited[nx][ny] = true
queue = append(queue, []int{nx, ny})
}
}
}
}模块三:操作系统(Day 10-14)
3.1 进程与线程
必须掌握的知识点:
进程
- 定义:程序的一次执行实例,是资源分配的基本单位
- 状态:创建 → 就绪 → 运行 → 阻塞 → 终止
- 进程控制块(PCB):进程ID、状态、程序计数器、寄存器、内存映射、打开的文件
- 进程间通信(IPC):
- 管道(pipe):半双工,父子进程
- 命名管道(FIFO):无亲缘关系进程
- 消息队列:内核维护的消息链表
- 共享内存:最快的 IPC 方式(需要同步机制)
- 信号量(semaphore):计数器,用于进程同步
- Socket:网络通信,最通用
线程
- 定义:CPU 调度的基本单位,共享进程的地址空间
- 线程 vs 进程:
- 进程有独立地址空间,线程共享
- 线程切换成本 < 进程切换(不需要切换页表)
- 线程间通信更方便(共享内存),但需要同步
- 用户态线程 vs 内核态线程:
- 用户态:协程/goroutine,调度在用户空间
- 内核态:OS 线程,由内核调度
- Go 的模型:M:N(多个 goroutine 映射到多个 OS 线程)
协程(对应 Go 的 goroutine)
- 更轻量:KB 级栈空间 vs 线程 MB 级
- 用户态调度:无需系统调用
- 适合 I/O 密集型场景
八股文要点:
- "进程和线程的区别?"
- "进程间通信有哪些方式?各自的优缺点?"
- "为什么线程切换比进程切换快?" → 不需要切换地址空间(页表)、TLB 不失效
- "协程的优势是什么?Go 的 goroutine 和一般协程有什么区别?" → 可被抢占、可并行(多 M)
- "僵尸进程和孤儿进程是什么?怎么处理?"
3.2 CPU 调度
必须掌握的知识点:
- 调度目标:公平性、吞吐量、响应时间、CPU 利用率
- 常见调度算法:
- FCFS(先来先服务):简单但可能导致"护航效应"
- SJF(最短作业优先):平均等待时间最短,但可能饥饿
- 时间片轮转(RR):公平,时间片大小影响性能
- 优先级调度:可抢占/不可抢占
- 多级反馈队列(MLFQ):Linux CFS 的基础思想
- Linux CFS(完全公平调度器):
- 基于虚拟运行时间(vruntime)
- 红黑树管理就绪队列
- 选择 vruntime 最小的进程运行
- 上下文切换:
- 保存/恢复 CPU 寄存器、程序计数器、栈指针
- 切换页表(进程切换时)
- 刷新 TLB(进程切换时)
八股文要点:
- "什么是上下文切换?开销在哪里?"
- "Linux 用什么调度算法?CFS 怎么保证公平?"
- "时间片设多大合适?设太大/太小各有什么问题?"
3.3 内存管理
必须掌握的知识点:
虚拟内存
- 每个进程有独立的虚拟地址空间(32 位:4GB,64 位:极大)
- 虚拟地址 → 物理地址:通过页表映射
- 好处:
- 进程隔离(互不影响)
- 超过物理内存的程序也能运行(swap)
- 共享内存(多进程映射同一物理页)
分页机制
- 页(Page):虚拟内存的基本单位,通常 4KB
- 页框(Frame):物理内存的基本单位
- 页表:虚拟页号 → 物理页框号
- 多级页表:减少页表内存占用(只分配用到的部分)
- TLB(Translation Lookaside Buffer):
- 页表的高速缓存
- 命中:1 个时钟周期
- 未命中:访问内存中的页表(数十~数百周期)
页面置换算法
- OPT(最优置换):替换未来最长时间不用的页(理想,不可实现)
- FIFO(先进先出):可能 Belady 异常(增加页框反而更多缺页)
- LRU(最近最少使用):替换最久未使用的页(实际常用近似实现)
- Clock(时钟算法):LRU 的近似,用访问位循环扫描
- LFU(最不经常使用):替换访问次数最少的页
进程地址空间布局
高地址
┌─────────────┐
│ 内核空间 │
├─────────────┤
│ 栈 │ ← 向下增长
│ ↓ │
│ │
│ ↑ │
│ 堆 │ ← 向上增长(malloc/new)
├─────────────┤
│ BSS 段 │ ← 未初始化全局变量
├─────────────┤
│ 数据段 │ ← 已初始化全局变量
├─────────────┤
│ 代码段 │ ← 只读,可共享
└─────────────┘
低地址八股文要点:
- "什么是虚拟内存?为什么需要虚拟内存?"
- "页表是什么?多级页表解决什么问题?"
- "TLB 是什么?为什么进程切换要刷新 TLB?"
- "LRU 怎么实现?时间复杂度?" → HashMap + 双向链表,O(1)
- "malloc 的底层实现?" → 小块 brk(堆扩展),大块 mmap
- "栈和堆的区别?" → 栈自动管理/连续/快;堆手动管理/碎片/慢
- "什么是内存泄漏?Go 中怎么排查?" → pprof heap profile
3.4 文件系统与 I/O
必须掌握的知识点:
文件系统
- inode:存储文件元数据(权限、大小、时间、数据块指针)
- 目录:特殊文件,存储 文件名→inode 的映射
- 硬链接 vs 软链接:
- 硬链接:多个文件名指向同一 inode
- 软链接:一个文件存储另一个文件的路径(类似快捷方式)
- 文件系统类型:ext4、XFS、ZFS
I/O 模型(Linux)
- 阻塞 I/O:进程阻塞直到数据就绪
- 非阻塞 I/O:立即返回,轮询检查
- I/O 多路复用:
- select:fd 数量有限(1024),每次需要遍历所有 fd
- poll:无数量限制,但仍需遍历
- epoll:事件驱动,O(1) 获取就绪 fd,Go 网络底层使用
- epoll_create:创建 epoll 实例
- epoll_ctl:注册/修改/删除监听事件
- epoll_wait:等待事件就绪
- 两种触发模式:LT(水平触发)、ET(边缘触发)
- 信号驱动 I/O
- 异步 I/O(AIO):内核完成后通知进程
零拷贝
- 传统读写:磁盘 → 内核缓冲区 → 用户缓冲区 → Socket 缓冲区 → 网卡(4 次拷贝)
- sendfile:磁盘 → 内核缓冲区 → 网卡(2 次拷贝,零用户态拷贝)
- mmap:将文件映射到用户空间,减少一次拷贝
八股文要点:
- "select、poll、epoll 的区别?"
- "epoll 的 LT 和 ET 有什么区别?" → LT 只要就绪就通知;ET 仅状态变化时通知一次
- "为什么 Go 的网络 I/O 能做到高并发?" → goroutine + netpoller(epoll)
- "什么是零拷贝?应用场景?"
- "inode 是什么?一个文件的 inode 包含哪些信息?"
3.5 锁与同步
必须掌握的知识点:
锁的类型
- 互斥锁(Mutex):同一时刻只有一个线程持有
- 读写锁(RWMutex):多读一写
- 自旋锁(Spinlock):忙等待,适合临界区极短的场景
- 乐观锁 vs 悲观锁:
- 悲观锁:先加锁再操作(Mutex)
- 乐观锁:先操作再检查冲突(CAS)
死锁
- 四个必要条件:
- 互斥:资源不能共享
- 持有并等待:持有资源的同时请求新资源
- 不可剥夺:已持有的资源不能被强制回收
- 循环等待:存在资源请求的环形链
- 解决方法:
- 预防:破坏四个条件之一(如固定加锁顺序破坏循环等待)
- 避免:银行家算法(检查安全状态)
- 检测:资源分配图
- 恢复:杀死进程 / 回滚
原子操作
- CAS(Compare-And-Swap):
if *addr == old { *addr = new; return true } - Go 的
sync/atomic包:AddInt64、CompareAndSwapInt64、LoadInt64、StoreInt64 - ABA 问题:值从 A→B→A,CAS 认为没变。解决:加版本号
八股文要点:
- "死锁的四个条件?怎么避免?"
- "CAS 是什么?有什么问题?" → ABA 问题、自旋开销、只能保护一个变量
- "互斥锁和自旋锁的区别?什么时候用自旋锁?" → 临界区极短(< 几μs)
- "Go 的 Mutex 有哪两种模式?" → 正常模式(自旋+竞争)和饥饿模式(FIFO)
3.6 网络基础(必考)
必须掌握的知识点:
TCP/IP 分层模型
应用层:HTTP、DNS、FTP
传输层:TCP、UDP
网络层:IP、ICMP、ARP
数据链路层:以太网
物理层TCP 三次握手
客户端 服务端
|--- SYN(seq=x) --->| 第一次:客户端发起连接请求
|<-- SYN+ACK(seq=y, ack=x+1) --| 第二次:服务端确认并请求连接
|--- ACK(ack=y+1) -->| 第三次:客户端确认- 为什么三次?→ 防止失效的连接请求到达服务端导致资源浪费
TCP 四次挥手
主动方 被动方
|--- FIN --->| 第一次:主动方请求关闭
|<-- ACK ----| 第二次:被动方确认
|<-- FIN ----| 第三次:被动方也请求关闭
|--- ACK --->| 第四次:主动方确认
[TIME_WAIT 2MSL]- 为什么四次?→ TCP 全双工,每个方向需要单独关闭
- 为什么 TIME_WAIT?→ 确保最后一个 ACK 到达 + 让旧连接的报文消散
TCP 可靠性保证
- 序列号 + 确认号
- 超时重传(RTO)
- 滑动窗口(流量控制)
- 拥塞控制:慢启动 → 拥塞避免 → 快重传 → 快恢复
TCP vs UDP
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠(重传、排序) | 不可靠 |
| 速度 | 较慢 | 较快 |
| 场景 | HTTP、文件传输 | DNS、视频流、游戏 |
HTTP
- HTTP/1.1:持久连接、管线化(但有队头阻塞)
- HTTP/2:多路复用、头部压缩、服务端推送、二进制帧
- HTTP/3:基于 QUIC(UDP)、解决 TCP 队头阻塞
- HTTPS = HTTP + TLS:
- TLS 握手:非对称加密交换密钥 → 对称加密通信
- 证书验证:CA 签名、证书链
DNS
- 域名 → IP 地址的解析
- 递归查询 vs 迭代查询
- 缓存层级:浏览器 → OS → 本地DNS → 根DNS → 顶级域DNS → 权威DNS
八股文要点:
- "TCP 三次握手的过程?为什么不是两次/四次?"
- "TCP 四次挥手?TIME_WAIT 的作用?TIME_WAIT 过多怎么办?"
- "TCP 怎么保证可靠传输?"
- "TCP 和 UDP 的区别?各自适用场景?"
- "HTTP/1.1、HTTP/2、HTTP/3 的演进?解决了什么问题?"
- "HTTPS 的加密过程?"
- "输入 URL 到页面显示的全过程?" → DNS → TCP → HTTP → 渲染
模块四:八股文速查卡片
数据结构与算法八股
| # | 问题 | 核心答案 |
|---|---|---|
| 1 | 数组 vs 链表 | 数组连续内存O(1)随机访问;链表非连续O(1)增删 |
| 2 | HashMap 原理 | 数组+链表/红黑树;哈希定位+链地址法解冲突 |
| 3 | 红黑树用在哪 | Java HashMap(>8)、Linux CFS、C++ map |
| 4 | B+树为什么适合数据库 | 叶子有序链表适合范围查询;扇出大减少I/O |
| 5 | 堆的应用 | TopK、优先队列、堆排序 |
| 6 | 快排最坏情况 | 已排序数组+固定pivot → O(n²),用随机pivot优化 |
| 7 | DP vs 贪心 | DP枚举所有子问题取全局最优;贪心取局部最优 |
| 8 | DFS vs BFS | DFS用栈/递归深度优先;BFS用队列广度优先找最短路 |
操作系统八股
| # | 问题 | 核心答案 |
|---|---|---|
| 1 | 进程 vs 线程 | 进程=资源分配单位(独立地址空间);线程=调度单位(共享地址空间) |
| 2 | 协程优势 | 用户态调度、KB级栈、创建成本极低、适合I/O密集 |
| 3 | 死锁条件 | 互斥+持有等待+不可剥夺+循环等待,破坏任一可预防 |
| 4 | 虚拟内存 | 进程独立地址空间,页表映射到物理内存,提供隔离+超额分配 |
| 5 | 页面置换 | LRU最常考:HashMap+双向链表实现O(1);Clock是近似LRU |
| 6 | select vs epoll | select:O(n)遍历+1024限制;epoll:事件驱动O(1)+无限制 |
| 7 | TCP可靠性 | 序列号+确认+重传+滑动窗口+拥塞控制 |
| 8 | 三次握手原因 | 防止失效SYN到达服务端建立无效连接浪费资源 |
| 9 | TIME_WAIT | 2MSL等待:确保最后ACK到达+旧报文消散 |
| 10 | HTTPS加密 | 非对称密钥交换(RSA/ECDHE)→ 对称加密通信(AES) |
| 11 | 零拷贝 | sendfile/mmap避免用户态拷贝,用于文件传输/消息队列 |
| 12 | CAS | 比较并交换,乐观锁实现;ABA问题用版本号解决 |
每日学习计划
| 天数 | 主题 | 上午(3h) | 下午(3h) | 晚上(2h) |
|---|---|---|---|---|
| Day 1 | 数组/链表 | 学习数据结构原理 | LeetCode 刷题 5-6 道 | 整理八股笔记 |
| Day 2 | 栈/队列/哈希表 | 学习原理 + 单调栈 | LeetCode 刷题 5-6 道 | 面试题模拟 |
| Day 3 | 树/图 | 二叉树遍历+BST | LeetCode 刷题 5-6 道 | 堆/Trie/图论 |
| Day 4 | 排序/二分 | 手写快排归并 | LeetCode 二分题 5 道 | 面试题模拟 |
| Day 5 | 双指针/滑窗 | 学习模板 | LeetCode 刷题 6 道 | 复习 |
| Day 6 | 回溯 | 学习模板+剪枝 | LeetCode 回溯题 5 道 | 面试题模拟 |
| Day 7 | 动态规划(上) | 线性DP+背包 | LeetCode DP 题 5 道 | 复习 |
| Day 8 | 动态规划(下) | 序列DP+二维DP | LeetCode DP 题 5 道 | 面试题模拟 |
| Day 9 | 贪心/BFS/DFS | 贪心+图搜索 | LeetCode 综合刷题 | 算法总复习 |
| Day 10 | 进程/线程/协程 | 学习原理 | 整理八股文 | 对比Go的goroutine |
| Day 11 | CPU调度/同步 | 调度算法+锁 | 死锁+CAS | 面试题模拟 |
| Day 12 | 内存管理 | 虚拟内存+分页 | 页面置换+地址空间 | 复习 |
| Day 13 | I/O与文件系统 | I/O模型+epoll | 零拷贝+文件系统 | 面试题模拟 |
| Day 14 | 网络 | TCP/UDP/HTTP | DNS+HTTPS | 全阶段总复习 |
LeetCode 刷题计划(总计 70+ 题)
第一周:数据结构 + 基础算法(Day 1-5)
- 链表:206, 21, 142, 19, 2, 23, 146 → 7 题
- 栈/队列:20, 155, 739, 239, 347, 232 → 6 题
- 哈希:1, 49, 128, 3 → 4 题
- 树:102, 226, 104, 98, 236, 199, 208 → 7 题
- 二分:33, 34, 35, 162, 240 → 5 题
- 双指针/滑窗:15, 11, 76, 438, 209 → 5 题
第二周:高级算法 + OS(Day 6-9)
- 回溯:46, 39, 78, 17, 22, 51 → 6 题
- DP:70, 300, 322, 1143, 72, 198, 62, 139, 5 → 9 题
- 贪心:55, 45, 435, 56, 135 → 5 题
- 图/BFS/DFS:200, 207, 210, 994, 133 → 5 题
- 综合练习:补刷弱项 → ~10 题
合计约 70 题(配合第一阶段已有的并发编程练习代码)
推荐学习资源
算法
- 代码随想录 — 分类刷题最佳路径
- LeetCode 热题 100 — 面试核心题集
- 《算法导论》— 参考书(不需要通读,查概念用)
操作系统
- 小林 coding - 图解系统 — 最适合面试的 OS 资料
- 小林 coding - 图解网络 — 网络部分必看
- 《现代操作系统》— 经典教材(选读)
- 《UNIX 环境高级编程》— 进阶参考
八股文复习
- CS-Notes — 计算机基础面试笔记
- 小林 coding 全站 — 图文并茂,面试向
阶段验收标准
完成以下全部内容视为通过第二阶段:
- [ ] 能手写快速排序和归并排序(10 分钟内)
- [ ] 能手写二分查找及左右边界变体(5 分钟内)
- [ ] 能手写 BFS/DFS 模板(5 分钟内)
- [ ] 能手写滑动窗口模板(5 分钟内)
- [ ] LeetCode 中等难度正确率 > 70%(限时 30 分钟)
- [ ] 能口述进程/线程/协程区别及 Go 的 M:N 模型
- [ ] 能口述虚拟内存 + 分页 + TLB 完整机制
- [ ] 能口述 TCP 三次握手/四次挥手全过程及原因
- [ ] 能口述 select/poll/epoll 区别及 Go netpoller 原理
- [ ] 能口述死锁四条件 + 解决方案
- [ ] 能画出进程地址空间布局图
- [ ] 能解释 HTTP/1.1 → HTTP/2 → HTTP/3 的演进动机