无法跳转到题目请在右侧目录索引中输入对应题目的序号点击即可
答法思路:最终一致性 + 双写问题控制:
- 可使用延迟双删策略(写 MySQL 后删缓存,再延迟一次删)
- 或使用Binlog + 消费队列异步更新缓存(如 Canal)
- 更高级可用 缓存更新消息队列 + 异步重建缓存
领域驱动设计(DDD):将系统按业务模型组织,提升复杂系统可维护性。
- 实体(Entity):有唯一标识(ID),生命周期长,如用户、订单
- 值对象(Value Object):无 ID、只关心值,如地址、金额
- 聚合(Aggregate):一组相关对象组成的业务一致性边界
- 聚合根(Aggregate Root):代表聚合的唯一入口,负责聚合内部规则和约束
- 添加合适索引(走索引避免全表扫描)
- 避免
SELECT *,限制返回字段 - 拆分页/limit深分页,用索引覆盖
- 使用
EXPLAIN分析执行计划 - 表结构优化、分库分表、预加载热数据
- 数据物理顺序 = 索引顺序,存储在一棵B+树中
- 每张表只能有一个聚簇索引(InnoDB 主键默认是)
- 非聚簇索引会存储主键作为“指针”
- 没有遵循最左前缀原则
- 中间字段跳过查询
- 使用了函数/隐式类型转换
- 出现范围查询(如
>)后,右边字段索引失效
- RDB:快照方式定期保存内存状态到磁盘
- AOF:将写操作日志持续追加到文件,重启时回放
- 可混合使用,兼顾恢复速度和数据完整性
- 一种有序链表+多级索引结构,支持快速查找(O(logN))
- Redis SortedSet 内部结构就是跳表
| 问题 | 描述 | 解决方案 |
|---|---|---|
| 穿透 | 请求数据缓存和数据库都没有 | 缓存空值 / 布隆过滤器 |
| 击穿 | 热点 key 过期,瞬间大量请求打到数据库 | 分布式锁 / 提前刷新 |
| 雪崩 | 大量 key 同时过期 | 设置随机过期时间 / 多级缓存 / 限流降级 |
-
三次握手:
- 客户端:SYN
- 服务端:SYN-ACK
- 客户端:ACK → 确保双方都能发送和接收
-
四次挥手:
- 客户端:FIN
- 服务端:ACK
- 服务端:FIN
- 客户端:ACK → 连接被双方优雅关闭
- 接收窗口(rwnd):接收方告诉发送方自己还能接收多少字节
- 拥塞窗口(cwnd):发送方根据网络拥塞状况控制的窗口,避免网络堵塞
- 真实发送窗口 =
min(rwnd, cwnd)
- 发生丢包时(超时或三次重复 ACK),TCP 进入 快速重传/拥塞避免阶段
- 拥塞窗口
cwnd会减半,触发 乘法减小算法(AIMD)
CDN(内容分发网络)核心是就近加速,通过把内容缓存到各地边缘节点,提升访问速度、减轻源站压力。
- 加速对象:静态资源(HTML/CSS/JS/图片/视频)为主
- 工作流程:DNS 解析调度 → 用户访问最近边缘节点 → 命中缓存或回源
- CDN 厂商:阿里云 CDN、腾讯云 CDN、Cloudflare、Akamai 等
| 特性 | TCP | UDP |
|---|---|---|
| 是否连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠,顺序、重传、纠错 | 不可靠,可能丢包乱序 |
| 应用场景 | 文件传输、HTTP、数据库等 | 视频直播、游戏、DNS 等 |
| 速度 | 慢(要确认) | 快(小包高频) |
- UDP + RTP + SRTP → 实时传输音视频数据(低延迟)
- WebRTC:浏览器实时通信协议,底层基于 UDP + ICE/STUN/TURN
- 信令通信(非媒体)可用 TCP/HTTP/WebSocket
- HTTPS = HTTP + TLS/SSL 加密层
- HTTP 是明文传输,HTTPS 是加密传输
- HTTPS 需要数字证书进行身份验证
- HTTPS 更安全,防劫持、防篡改、防监听
- 公钥(Public Key)
- 颁发者(Issuer)CA 签名者信息
- 使用者(Subject)你是谁
- 有效期(Valid From / To)
- 证书序列号、签名算法
- 可通过
openssl或浏览器查看证书详情
| 协议 | 特点 | 应用场景 |
|---|---|---|
| RTMP | 基于 TCP,延迟低,传输稳定 | 主流推流协议 |
| HLS | 基于 HTTP,切片传输,延迟高 | 点播、直播回看 |
| FLV | HTTP-FLV,延迟较低,兼容好 | 网页直播 |
| SRT | 新一代低延迟直播传输协议,抗丢包强 | 专业直播、大厂部署 |
| WebRTC | 端到端实时通信,超低延迟 | 互动直播、音视频通话 |
- HashMap 线程不安全,效率高;Hashtable 线程安全(方法加锁),但性能差;
- HashMap 允许
null键和值,Hashtable 不允许。
- 使用 ConcurrentHashMap 替代:分段锁/锁分离/红黑树优化,性能远超 Hashtable
- JDK8 以后用 CAS + synchronized 替代 segment 更高效。
- 线程是进程的执行单元,共享内存;进程是资源分配的最小单位,独立地址空间。
- 线程轻量、切换快;进程隔离好、稳定性高。
- 线程间通信可直接读写共享变量(需加锁)
- 进程间通信需用 IPC机制:管道、消息队列、共享内存、socket 等
- InnoDB 使用 B+树 索引,主键为聚簇索引,其他是二级索引
- 索引底层是磁盘优化的平衡多叉树结构
- B树数据存储在所有节点,B+树只存在叶子节点,且有链表连接
- B+树结构更适合磁盘顺序读写
- 所有数据在叶子节点有序链表中,直接按链表遍历即可,无需回溯,效率更高
| 隔离级别 | 问题 |
|---|---|
| Read Uncommitted | 会出现脏读 |
| Read Committed | 避免脏读,但有不可重复读 |
| Repeatable Read(默认) | 避免不可重复读,但有幻读 |
| Serializable | 最严格,防幻读,性能差 |
- 多版本并发控制,基于隐藏字段(创建版本号 + 删除版本号)
- 快照读 + undo log,实现 读不加锁、并发高性能
LIKE '%xxx'左模糊- 隐式类型转换、函数包裹字段
- 未走最左前缀、范围后字段失效
or条件某字段无索引
- RDB:定时快照,重启恢复快
- AOF:记录操作日志,安全性高
- 支持 RDB+AOF 混合使用
| 问题 | 描述 | 解决方案 |
|---|---|---|
| 雪崩 | 大量 key 同时失效 | 随机过期时间 + 限流 +降级 |
| 穿透 | 查不到的数据反复打数据库 | 缓存空值 / 布隆过滤器 |
| 击穿 | 热点 key 失效瞬时并发过高 | 分布式锁 / 本地互斥重建缓存 |
- 三次握手:SYN → SYN-ACK → ACK,确保双向通信可达
- 四次挥手:FIN → ACK → FIN → ACK,连接双向关闭,确保可靠释放
ls、cd、pwd、rm、cp、mvps -aux、top、grep、find、tail -fchmod、chown、netstat、curl、ping
-
常用命令:
docker build构建镜像docker run启动容器docker ps查看容器docker exec进容器docker logs查看日志
-
构建镜像:写
Dockerfile,定义基础镜像、拷贝文件、启动命令等 示例:FROM openjdk:8 COPY app.jar /app.jar CMD ["java", "-jar", "/app.jar"]
答法:基于消息唯一标识 + 幂等性处理
-
MQ(如 Kafka、RocketMQ)可能因网络或消费失败重投
-
解决:生产消息加唯一 ID(如订单号、UUID),消费端做去重
- 可结合 Redis/数据库记录已处理 ID
- 消费端实现幂等消费逻辑,避免重复处理同一消息
核心:业务操作具备“重复执行效果相同”的能力
- 给每条消息添加 唯一业务 ID(如订单号、交易号)
- 消费前先判断该 ID 是否已处理(查 Redis/数据库日志表)
- 如果处理过则跳过,没处理就执行操作并记录处理状态
答法:引入“消费确认机制 + 异步回查”
-
若使用 Redis 作为幂等标记:
- 消息 key 已写,但业务未执行,系统不一致
-
解决方案:
- 消息重试 + 幂等处理(确保业务可重入)
- 结合消息事务机制或中间件支持的事务消息(如 RocketMQ 事务消息)
- 使用可靠事件队列 + 补偿机制
-- 单列索引
CREATE INDEX idx_name ON table_name (column_name);
-- 联合索引(多个字段)
CREATE INDEX idx_name ON table_name (col1, col2);
-- 唯一索引
CREATE UNIQUE INDEX idx_name ON table_name (column_name);答法:HTTPS加密通信底层就是 TLS
-
Client Hello:客户端发起请求,附带支持的加密套件 + 随机数
-
Server Hello:服务端返回随机数 + 服务器数字证书(含公钥)
-
客户端验证证书:是否可信、合法、过期
-
生成对称密钥:
- 客户端用服务器公钥加密对称密钥发送给服务器
- 服务端用私钥解密获取该密钥
-
握手完成,切换对称加密传输(如 AES)
CREATE INDEX idx_name ON student(name);- 主键索引是聚簇索引,数据按主键顺序存储
- 普通索引是非聚簇索引,只存键值和主键指针
- 非聚簇索引存的不是完整数据,而是主键指针,需要回主键索引取整行数据
- RU:脏读
- RC:不可重复读
- RR(默认):幻读
- Serializable:最安全但效率最低
- 主库将写操作记录进 binlog → 从库 IO 线程读取 → SQL 线程回放执行 → 实现同步
- MySQL 所有更改数据的操作(Insert/Update/Delete)的二进制日志
- 多个 key 同时过期导致大量请求打到数据库,可能造成宕机
- 解决: 加随机过期时间、构建多级缓存、限流降级
- RDB:快照持久化
- AOF:记录写操作日志
- 混合持久化:兼顾两者优势
- 请求报文 = 请求行 + 请求头 + 空行 + 请求体
- 响应报文 = 状态行 + 响应头 + 空行 + 响应体
- HTTPS 多了 TLS 加密,数据更安全
- 需要证书认证和握手过程
- Client Hello(支持的加密算法+随机数)
- Server Hello(证书+随机数)
- 客户端验证证书并生成对称密钥
- 客户端用公钥加密密钥发送给服务端
- 开始对称加密通信
- 非对称加密安全但慢,只用来传输对称密钥
- 对称加密快,用于大数据传输
- 两者结合兼顾安全与效率
封装、继承、多态
- 先用编译器编译成
.class字节码文件 - JVM 类加载器加载该文件
- 解释或 JIT 编译成机器码运行
- 跨平台运行 Java 字节码
- 管理内存(GC)、类加载、执行引擎、安全等
- 线程是轻量级的,多个线程共享进程资源
- 进程之间资源隔离,稳定性好但开销大
- 协程是用户态调度,不依赖系统线程调度器
- 更轻量,适合 I/O 密集型场景
- 切换只在用户态完成,无系统调用开销
- 占用资源更少,可同时运行成千上万协程
- 分为:代码段、数据段、堆、栈、BSS 段
- ELF 文件还有节区表、符号表、程序头等结构
uptime # 查看 load average- 第一行的
load average %CPU表示 CPU 占用情况
lscpu | grep 'CPU(s)'
cat /proc/cpuinfo | grep "processor" | wc -ldu -sh ./netstat -an | grep ESTABLISHED- 控制未确认数据量的窗口机制,保证数据有序、流量受控、吞吐高
- 主动断开连接的一方需要等待 2 倍 RTT,防止旧连接影响新连接
- TCP 是全双工,双方都要发送一次 FIN 和 ACK,才能完全断开连接
- 第二次握手由服务端发
SYN + ACK一起,合并为一个报文段,节省一次传输
- 使用 Linux 的 Namespace(命名空间)和 CGroup(资源配额)机制
- 实现网络、进程、文件系统等资源隔离
- 编写 Dockerfile(FROM、COPY、RUN、CMD 等)
docker build -t xxx .构建镜像docker run运行容器
数据准备 → 模型初始化 → 前向传播 → 反向传播(BP)→ 梯度更新 → 多轮训练 → 保存模型
常见解释有:
- Memory Consistency Protocol:多核内存一致性协议
- Multi-Channel Processing:多通道处理架构
- ChatGPT、GitHub Copilot、HuggingFace、Midjourney、Stable Diffusion、LangChain、PaddlePaddle 等
用过,不是线程安全的,多个线程同时修改会出现数据不一致或异常。
常见方式有三种:
- 使用 Collections.synchronizedList 包装:
List<String> list = Collections.synchronizedList(new ArrayList<>());-
使用 CopyOnWriteArrayList:适合读多写少场景
-
手动加锁(synchronized 或 ReentrantLock)
避免死锁的常用方法:
- 资源加锁顺序一致
- 减少锁的持有时间
- 加锁时使用 tryLock 设置超时
- 检测与恢复:使用工具(如 jconsole)定位死锁线程
| 对比项 | 进程 | 线程 |
|---|---|---|
| 内存 | 拥有独立内存空间 | 共享进程内存 |
| 开销 | 创建、切换开销大 | 开销小 |
| 稳定性 | 一个崩溃不影响其他进程 | 一个崩溃可能影响进程中其他线程 |
| 通信 | 需要 IPC(进程间通信) | 共享变量即可通信 |
- 同一进程中的线程共享:内存地址空间、堆内存、方法区、静态变量、文件描述符等
- 不共享的有:线程栈(局部变量)、程序计数器、虚拟机栈
JVM 垃圾回收依赖GC算法 + 分代思想:
- 新生代:使用 复制算法(如 Minor GC)
- 老年代:使用 标记-清除/标记-整理(如 Major GC)
- 常用 GC 收集器:G1、ZGC、CMS、Serial、Parallel
判断对象是否可回收:
- 引用计数法(已废弃)
- 可达性分析(主流)
按照粒度分类:
- 表级锁:如 MyISAM 的锁,开销小,冲突多
- 行级锁:如 InnoDB 的锁,开销大,冲突少
按照类型分类:
- 共享锁(S 锁):允许读,不允许写
- 排他锁(X 锁):允许自己读写,其他线程不能读写
- 意向锁:表级锁,标记事务是否打算加行锁(加快锁冲突检测)
- 乐观锁/悲观锁(逻辑层面的控制手段)
缓存一致性常用方案:
- 先更新数据库,再删缓存(推荐)
- 延迟双删机制:先删缓存 → 更新数据库 → 延迟再删一次缓存
- 加分布式锁、消息队列补偿机制 处理并发/失败情况
-
添加/优化索引
-
使用 EXPLAIN 查看执行计划
-
避免 **select ***,减少扫描字段
-
加 limit,分页时用 索引字段过滤
-
拆大表、归档历史数据
-
设置慢查询日志排查:
SET global slow_query_log = 1;
-
使用函数或运算:
WHERE YEAR(create_time) = 2024
-
字段类型不一致(隐式转换)
-
模糊查询左模糊:
WHERE name LIKE '%abc'
-
多列索引不满足最左匹配原则
-
使用 or 且字段未都建索引
- 客户端发送 SYN 报文
- 服务端返回 SYN + ACK
- 客户端回复 ACK,连接建立完成
为什么三次?
- 保证双方都收到了彼此的能力确认(发送/接收)
- 慢启动(初始小窗口,指数增长)
- 拥塞避免(线性增长)
- 快速重传(三次 dup-ack 立即重传)
- 快速恢复(调整拥塞窗口)
CDN(内容分发网络)主要作用:
- 将静态内容(图片/视频)缓存到离用户更近的边缘节点
- 加速访问、减轻源站压力、提高高并发吞吐
- 基本流程:DNS调度 → 找最近节点 → 命中缓存或回源
题意: 找到数组中三元组满足 a + b + c = 0,不能重复 解法: 排序 + 双指针
def threeSum(nums):
nums.sort()
res = []
for i in range(len(nums)-2):
if i > 0 and nums[i] == nums[i-1]: continue
l, r = i+1, len(nums)-1
while l < r:
s = nums[i] + nums[l] + nums[r]
if s < 0: l += 1
elif s > 0: r -= 1
else:
res.append([nums[i], nums[l], nums[r]])
while l < r and nums[l] == nums[l+1]: l += 1
while l < r and nums[r] == nums[r-1]: r -= 1
l += 1; r -= 1
return res题意: 原地删除重复元素,返回去重后长度 解法: 快慢指针(双指针)
def removeDuplicates(nums):
if not nums: return 0
slow = 0
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1题意:
给定一棵二叉树和两个节点 p 和 q,找到它们的最近公共祖先。
方法一:递归法(自顶向下)
def lowestCommonAncestor(root, p, q):
if not root or root == p or root == q:
return root
left = lowestCommonAncestor(root.left, p, q)
right = lowestCommonAncestor(root.right, p, q)
if left and right: return root
return left if left else right方法二:哈希 + 父指针
- 先用 DFS 构建
node -> parent映射 - 找出 p 的所有祖先存进 set
- 从 q 向上找,遇到第一个在 set 中的即为 LCA
def lowestCommonAncestor(root, p, q):
parent = {}
def dfs(node):
if node.left:
parent[node.left] = node
dfs(node.left)
if node.right:
parent[node.right] = node
dfs(node.right)
dfs(root)
ancestors = set()
while p:
ancestors.add(p)
p = parent.get(p)
while q not in ancestors:
q = parent.get(q)
return qDFS(深度优先搜索):
- 递归或栈实现
- 适合找路径、连通块、回溯、拓扑排序
示例:二维地图的岛屿数量
def numIslands(grid):
def dfs(i, j):
if i<0 or i>=len(grid) or j<0 or j>=len(grid[0]) or grid[i][j] != '1':
return
grid[i][j] = '0'
for x, y in [(0,1),(1,0),(-1,0),(0,-1)]:
dfs(i+x, j+y)
count = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == '1':
dfs(i, j)
count += 1
return countBFS(广度优先搜索):
- 使用队列,适合最短路径、分层搜索、拓扑排序
示例:无权图最短路径
from collections import deque
def bfs_shortest_path(graph, start, end):
visited = set()
queue = deque([(start, [start])])
while queue:
node, path = queue.popleft()
if node == end:
return path
visited.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
queue.append((neighbor, path + [neighbor]))
return []迭代法
思路:用两个指针 prev 和 curr,遍历链表,将 curr.next 指向 prev,逐步反转。
def reverseList(head):
prev = None
curr = head
while curr:
next_node = curr.next # 保存下一个节点
curr.next = prev # 反转指针
prev = curr # prev 后移
curr = next_node # curr 后移
return prev # prev 是新头节点递归法
思路:递归到底后开始反转指针,最后返回新的头节点。
def reverseList(head):
if not head or not head.next:
return head
new_head = reverseList(head.next)
head.next.next = head # 让后一个节点指向当前节点
head.next = None # 当前节点断开后续连接
return new_head题意
链表如 1 → 2 → 3 → 4,给定节点指针指向 2,要删除它,但没有头指针。
关键点
无法从头遍历,所以不能用常规删除。只能把后继节点数据复制到当前节点,再删掉后继节点。
代码实现
def deleteNode(node):
# node 不是尾节点
node.val = node.next.val # 复制后继节点值到当前节点
node.next = node.next.next # 删除后继节点题意
返回二叉树从右侧看到的节点值序列。
思路
层序遍历(BFS),每层取最后一个节点。
代码实现
from collections import deque
def rightSideView(root):
if not root:
return []
res = []
queue = deque([root])
while queue:
size = len(queue)
for i in range(size):
node = queue.popleft()
# 每层最后一个节点加入结果
if i == size - 1:
res.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return res题意
普通层序遍历基础上,遇到空节点用 * 代替,输出完整的层序结果。
思路
- 使用队列做层序遍历
- 遇到节点为空,加入
*并继续占位 - 为防止无限扩展,遍历时判断当前层是否全为
None,如果是则停止
代码实现(Python)
from collections import deque
def levelOrderWithNull(root):
if not root:
return []
res = []
queue = deque([root])
while queue:
level_size = len(queue)
level_vals = []
all_none = True # 记录本层是否全是 None
for _ in range(level_size):
node = queue.popleft()
if node:
level_vals.append(node.val)
queue.append(node.left)
queue.append(node.right)
if node.left or node.right:
all_none = False
else:
level_vals.append('*')
queue.append(None)
queue.append(None)
# 如果本层全是 None,说明后面都是空,停止遍历
if all_none and all(v == '*' for v in level_vals):
break
res.extend(level_vals)
# 剔除末尾多余的 '*'
while res and res[-1] == '*':
res.pop()
return res- 解析 HTML,构建 DOM 树
- 解析 CSS,构建 CSSOM 树
- 合成渲染树(Render Tree),结合 DOM 和 CSSOM
- 布局(Reflow),计算每个节点大小和位置
- 绘制(Repaint),将节点绘制到屏幕上
- 加载和执行 JS 会阻塞渲染,可能引发重排重绘
- 重排(Reflow):DOM 结构或样式改变导致布局改变时发生,成本大
- 重绘(Repaint):只影响颜色、visibility 等视觉样式改变时发生,成本较小
避免方法:
- 减少频繁修改布局相关属性(如 width、height、margin)
- 批量修改样式,避免逐条修改触发多次重排
- 使用
transform、opacity做动画,避免触发布局变更 - 使用文档碎片或隐藏元素批量操作 DOM
- 浏览器 DOM 树:真实的、由浏览器解析 HTML 生成
- 虚拟 DOM:Vue 用 JS 对象描述的 DOM 树的轻量副本,存于内存
- Vue 修改虚拟 DOM 后,经过 Diff 算法计算出差异,再最小化操作更新真实 DOM,提升性能
- 标签名(tag)
- 属性(props)和样式
- 子节点(children)
- 唯一 key(用于优化 diff)
- 组件状态等元数据
- 新元素对应的虚拟节点插入到对应位置
- Diff 算法比较新旧虚拟 DOM,找出最小差异
- 只对比变动节点,执行对应的增删改操作更新真实 DOM
- 逐层比较新旧虚拟 DOM
- 利用 key 优化节点匹配
- 通过最小编辑距离策略,尽量减少节点移动和删除重建
- 保持子树顺序,避免不必要的 DOM 操作,提高性能
- BFC 是一种独立的渲染区域,内部元素布局互不影响外部
- 触发条件:
overflow非visible、display: flow-root、浮动元素、绝对定位等 - BFC 作用:清除内部浮动、避免外边距重叠、限制元素的盒子范围
- 是理解布局和避免布局问题的重要概念
- 闭包是函数和声明该函数的词法环境组合的结构
- 函数能访问其外部作用域变量,即使外部函数执行完毕
- 原理:JS 函数在创建时会保存作用域链,形成闭包
- 常用于封装私有变量、实现模块化和回调函数
const Singleton = (function() {
let instance;
function createInstance() {
return { /* 实例对象 */ };
}
return function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
})();调用 Singleton() 始终返回同一个实例。
- 使用 call/apply/bind 改变函数执行时的
this指向 - 使用 with(不推荐)
- 利用 eval 动态执行代码
- 利用闭包包裹不同作用域变量
- ES6 的箭头函数绑定定义时的
this,不受调用时影响
题意
给定一个字符串,找出其中最长的没有重复字符的子串长度。
思路
用滑动窗口 + 哈希表记录字符最后出现的位置,窗口内保持无重复。
代码实现(Python)
def lengthOfLongestSubstring(s):
char_index = {}
left = 0
max_len = 0
for right, char in enumerate(s):
if char in char_index and char_index[char] >= left:
left = char_index[char] + 1 # 移动左指针,跳过重复字符
char_index[char] = right
max_len = max(max_len, right - left + 1)
return max_len说明
left和right是滑动窗口左右边界char_index存储字符最近出现的位置- 复杂度 O(n),适合面试直接写