LFU缓存¶
概述¶
LFU(Least Frequently Used,最不经常使用)是一种经典的**缓存淘汰策略**。当缓存容量已满时,优先淘汰**访问频率最低**的数据;当多个数据频率相同时,淘汰**最久未使用**的那个。
核心特征:LFU缓存通过哈希表 + 频率桶 + 双向链表的组合,实现 O(1) 时间复杂度的 get 和 put 操作。相比LRU,LFU能更好地保护热点数据。
与LRU的对比¶
LRU vs LFU 淘汰策略
LRU (Least Recently Used)
• 淘汰标准: 最长时间未被访问
• 依据: 时间局部性原理
• 特点: 简单,但可能淘汰热点数据
示例: 访问序列 A, B, C, D, E, A, F (capacity=5)
LRU淘汰 B(最久未访问)→ 但 B 可能是热点数据!
LFU (Least Frequently Used)
• 淘汰标准: 访问频率最低(频率相同则最久未使用)
• 依据: 频率局部性原理
• 特点: 保护热点数据,但实现复杂
示例: 访问序列 A, B, A, C, A, B, D (capacity=3)
频率: A=3, B=2, C=1, D=1
LFU淘汰 C或D(频率最低为1,选最久未访问的)→ A 和 B 被保护!
LFU特点¶
1. 频率统计¶
LFU为每个数据维护访问频率计数器:
2. 双层淘汰¶
LFU使用两层标准进行淘汰:
3. O(1) 操作¶
使用多级数据结构实现 O(1) 操作:
4. 更公平地淘汰¶
LFU能更好地保护真正的热点数据:
原理详解¶
频率桶设计¶
每个频率对应一个双向链表,存储该频率的所有节点:
minFreq 维护¶
维护最小频率值,用于快速定位淘汰候选:
频率转移¶
访问节点时,将其从当前频率桶移到更高频率桶:
| Text Only | |
|---|---|
get 操作流程¶
flowchart TB
A[get key] --> B{keyMap中存在?}
B -->|否| C[返回 -1]
B -->|是| D[获取节点]
D --> E[记录当前频率 freq]
E --> F[从 freqMap freq 删除节点]
F --> G[节点频率 +1]
G --> H[将节点加入 freqMap freq+1 头部]
H --> I{freqMap freq 是否为空?}
I -->|是| J{freq == minFreq?}
J -->|是| K[minFreq++]
J -->|否| L[跳过]
I -->|否| L
K --> M[返回节点值]
L --> M
style C fill:#ffcccc
style M fill:#ccffcc
put 操作流程¶
flowchart TB
A[put key, value] --> B{keyMap中存在?}
B -->|是| C[更新节点值]
C --> D[执行频率转移<br/>同get操作]
D --> E[操作完成]
B -->|否| F{缓存是否已满?}
F -->|是| G[淘汰 minFreq 频率桶尾部节点]
G --> H[从 keyMap 删除对应 key]
H --> I[创建新节点 freq=1]
F -->|否| I
I --> J[加入 freqMap 1 头部]
J --> K[keyMap 添加映射]
K --> L[minFreq = 1]
L --> E
style E fill:#ccffcc
可视化演示¶
完整操作演示¶
| Text Only | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | |
频率转移动画¶
代码实现¶
| C | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
复杂度分析¶
时间复杂度¶
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| get | O(1) | 哈希表查找 + 链表删除/插入 |
| put | O(1) | 哈希表操作 + 链表删除/插入 |
| Text Only | |
|---|---|
空间复杂度¶
- O(capacity):存储 capacity 个节点
| Text Only | |
|---|---|
LRU vs LFU 详细对比¶
| 特性 | LRU | LFU |
|---|---|---|
| 淘汰依据 | 最近使用时间 | 使用频率 + 时间 |
| 实现复杂度 | 简单 | 较复杂 |
| 数据结构 | 哈希表 + 双向链表 | 哈希表 + 频率桶 + 链表 |
| 热点数据保护 | 较弱 | 较强 |
| 新数据保护 | 较强 | 较弱 |
| 时间复杂度 | O(1) | O(1) |
| 空间复杂度 | O(capacity) | O(capacity) |
| 适用场景 | 时间局部性 | 频率局部性 |
应用场景¶
1. 数据库缓存¶
2. CDN缓存¶
3. 搜索建议¶
4. 推荐系统¶
5. 操作系统页面置换¶
| Text Only | |
|---|---|
LFU 变体¶
LFU with Aging¶
防止旧数据频率过高,阻止新数据进入:
Window-LFU¶
只统计最近时间窗口内的访问频率:
| Text Only | |
|---|---|
参考资料¶
- 《操作系统概念》- 页面置换算法
- 《计算机系统:程序员的视角》- 缓存层次结构
- LeetCode 460. LFU缓存
- Redis 缓存淘汰策略
- CPU缓存替换策略研究