模式对比矩阵
有些模式看起来相似但用途不同。本指南对比了常见的易混淆模式对,帮你选择正确的工具。
缓存 vs 对象池
| 维度 | LRU 缓存 | 对象池 |
|---|---|---|
| 目的 | 加速重复查询 | 避免分配/GC 开销 |
| 淘汰 | 最近最少使用被移除 | 用户归还,重复使用 |
| 内容 | 只读的缓存值 | 可变的、可复用对象 |
| 命中率 | 取决于访问模式 | 100%(预分配) |
| 示例 | DNS 缓存、HTTP 缓存 | 数据库连接池、线程池 |
| 易混淆时 | 如果你"缓存"对象来复用 | → 那是对象池 |
Arena 分配器 vs 空闲链表
| 维度 | Arena 分配器 | 空闲链表 |
|---|---|---|
| 分配 | O(1) 指针碰撞 | O(1) 从空闲链弹出 |
| 释放 | 只能全部一次性释放 | 单个 O(1) 归还 |
| 碎片化 | 无(连续内存) | 可能(非连续) |
| 生命周期 | 阶段性(解析、渲染) | 逐对象 |
| 示例 | 编译器每次查询的 arena | 游戏实体回收 |
| 易混淆时 | 如果需要单独释放 | → 用空闲链表 |
观察者 vs Actor 模型
| 维度 | 观察者 | Actor 模型 |
|---|---|---|
| 通信 | 同步回调 | 异步消息 |
| 耦合 | 共享内存,同一线程 | 隔离状态,任意位置 |
| 扩展性 | 单进程 | 分布式系统 |
| 故障隔离 | 一个坏观察者阻塞所有 | Actor 独立崩溃 |
| 示例 | DOM 事件、React 状态 | Erlang/OTP、Akka |
| 易混淆时 | 如果观察者需要隔离 | → 用 Actor |
跳表 vs B+ 树
| 维度 | 跳表 | B+ 树 |
|---|---|---|
| 平衡 | 概率性(随机层级) | 确定性(分裂/合并) |
| 并发 | 可无锁实现 | 锁机制复杂 |
| 磁盘 I/O | 差(指针追踪) | 优秀(高扇出) |
| 实现 | 简单(约 100 行) | 复杂(500+ 行) |
| 示例 | Redis 有序集、LevelDB memtable | MySQL InnoDB、文件系统索引 |
| 易混淆时 | 内存有序数据 → 跳表 | 磁盘 → B+ 树 |
环形缓冲区 vs 队列 (FIFO)
| 维度 | 环形缓冲区 | 标准队列 |
|---|---|---|
| 容量 | 固定,预分配 | 动态,按需增长 |
| 溢出 | 覆盖最旧(或阻塞) | 分配更多内存 |
| 内存 | O(容量),无分配 | O(n),可能触发 GC |
| 场景 | 有界缓冲、音频流 | 无界任务队列 |
| 示例 | Linux io_uring、日志 | 消息队列、BFS |
| 易混淆时 | 容量已知 → 环形缓冲区 | 无界 → 队列 |
写时复制 vs 双缓冲
| 维度 | 写时复制 | 双缓冲 |
|---|---|---|
| 缓冲区数 | 1(写时克隆) | 2(始终分配) |
| 拷贝触发 | 发生变更时 | 从不(交换指针) |
| 读成本 | O(1) 始终 | O(1) 始终 |
| 写成本 | O(n) 首次写入克隆 | O(写入量) 写后缓冲 |
| 内存 | O(n) 仅在写入时 | O(2n) 始终 |
| 示例 | Linux fork()、Rc<T> | GPU 渲染、游戏循环 |
| 易混淆时 | 写入稀少 → CoW | 持续写入 → 双缓冲 |
熔断器 vs 限流器
| 维度 | 熔断器 | 限流器 |
|---|---|---|
| 保护 | 调用方免受故障下游影响 | 服务端免受过量流量影响 |
| 触发 | 失败次数阈值 | 请求数量阈值 |
| 方向 | 出站(你调用别人) | 入站(别人调用你) |
| 恢复 | 超时后自动恢复 | 令牌随时间补充 |
| 示例 | 微服务 → 数据库 | API 网关、登录限制 |
| 易混淆时 | 保护自己 → 熔断器 | 保护服务器 → 限流器 |
状态机 vs 策略/虚表
| 维度 | 状态机 | 虚表 |
|---|---|---|
| 分发依据 | 当前状态 | 对象类型 |
| 转换 | 显式、有守卫 | 不适用 |
| 状态数 | 运行时变化 | 编译时固定 |
| 验证 | "这个转换能发生吗?" | "调用哪个实现?" |
| 示例 | TCP 连接状态 | Trait 对象、接口 |
| 易混淆时 | 行为随时间变化 → 状态机 | 随类型变化 → 虚表 |
布隆过滤器 vs 哈希集合
| 维度 | 布隆过滤器 | 哈希集合 |
|---|---|---|
| 假阳性 | 有(可调节) | 无 |
| 假阴性 | 无 | 无 |
| 内存 | 约 10 位/元素 | 约 50+ 字节/元素 |
| 删除 | 不支持 | O(1) |
| 场景 | "肯定不在集合中"的快速检查 | 精确成员判断 |
| 示例 | Chrome 安全浏览、LSM 读过滤 | 去重、已访问 URL |
| 易混淆时 | 如果 1% 假阳性可接受 + 内存重要 | → 布隆过滤器 |
预写日志 vs 检查点
| 维度 | 预写日志 | 检查点 |
|---|---|---|
| 粒度 | 每个操作 | 周期性快照 |
| 恢复 | 从最后一个检查点回放 | 加载快照 + 回放剩余 WAL |
| 磁盘 | 无限增长(不截断时) | 每次快照固定大小 |
| 写成本 | O(1) 每次追加 | O(状态大小)/次 |
| 组合使用? | 是 — WAL 提供持久性 | 检查点限制回放长度 |
| 示例 | PostgreSQL WAL、Redis AOF | PostgreSQL 基础备份、Redis RDB |
背压 vs 限流器
| 维度 | 背压 | 限流器 |
|---|---|---|
| 方向 | 生产者 → 消费者(上游信号) | 外部 → 服务器(网关强制) |
| 机制 | 减慢/暂停生产者 | 丢弃或排队超额请求 |
| 范围 | 内部管道流控 | 外部 API 边界 |
| 适应性 | 动态(适应消费者速度) | 静态阈值(令牌/秒) |
| 示例 | Node.js stream .pipe()、Reactive Streams | Stripe API 25 req/sec、Nginx limit_req |
| 易混淆时 | 如果你控制生产者 → 背压 | 如果无法控制发送方 → 限流器 |
墓碑 vs 脏标记
| 维度 | 墓碑 | 脏标记 |
|---|---|---|
| 标记含义 | "此项已删除" | "此项需要重新计算" |
| 生命周期 | 永久存在直到压缩 | 重新计算后清除 |
| 目的 | 延迟物理删除 | 延迟昂贵的重算 |
| 可见性 | 读者必须跳过墓碑项 | 读者看到过期值直到重算 |
| 示例 | Cassandra tombstone、LevelDB 删除标记 | Chromium 布局失效、游戏变换 |
| 易混淆时 | 标记为"已消失" → 墓碑 | 标记为"需更新" → 脏标记 |
驻留 vs 享元
| 维度 | 驻留 | 享元 |
|---|---|---|
| 共享方式 | 相同值共享同一个实例 | 内在状态共享,外在状态不同 |
| 查找 | 全局表(哈希映射) | 工厂 + 缓存 |
| 标识 | 指针相等替代值相等 | 对象仍按值比较 |
| 可变性 | 不可变(必须) | 内在不可变,外在可变 |
| 示例 | Python sys.intern()、Rust Symbol、Java 字符串池 | 字体字形、游戏瓦片精灵、CSS 规则对象 |
| 易混淆时 | 如果所有实例完全相同 → 驻留 | 如果实例共享部分状态 → 享元 |
事件循环 vs 工作窃取
| 维度 | 事件循环 | 工作窃取 |
|---|---|---|
| 线程 | 单线程 + I/O 多路复用 | 多线程,每线程一个双端队列 |
| 适用场景 | I/O 密集型、高连接数 | CPU 密集型、递归/并行计算 |
| 调度 | 协作式(回调/Promise) | 窃取式从其他队列获取任务 |
| 延迟 | 一个慢回调阻塞所有 | 空闲线程窃取任务,保持均衡 |
| 示例 | Node.js、Nginx、Redis | Go 调度器、Java ForkJoinPool、Tokio |
| 易混淆时 | 主要 I/O + 少量 CPU → 事件循环 | CPU 密集 + 多核 → 工作窃取 |
访问者 vs 中间件链
| 维度 | 访问者 | 中间件链 |
|---|---|---|
| 遍历方式 | 遍历类型化节点的树/图 | 遍历线性管道 |
| 分发 | 按节点类型(双重分发) | 按注册顺序 |
| 扩展操作 | 新访问者 = 新操作 | 新中间件 = 新层 |
| 数据流 | 访问者累积结果 | 请求/响应流经管道 |
| 示例 | LLVM IR pass、AST 变换 | Express.js、Django、gRPC 拦截器 |
| 易混淆时 | 处理异构树 → 访问者 | 处理请求/响应管道 → 中间件 |