📖 推荐阅读:《Yocto项目实战教程:高效定制嵌入式Linux系统》
🎥 更多学习视频请关注 B 站:嵌入式Jerry
深入理解 LRU 缓存策略:从原理到实战
一、从现实问题出发:为什么需要 LRU?
在操作系统或嵌入式设备中,内存是有限的。
例如:
- 系统只能保留最近 N 页内存页,多余的要写入 swap 或丢弃
- 嵌入式终端的 Flash Cache 只能缓存有限条记录
- 浏览器的历史记录不可能无限保留
这就引出一个实际问题:
当缓存空间已满,要新加入一个元素,应该淘汰哪一个?
一种直观式、实用的答案是:
淘汰“最近最少使用”的那个。
二、什么是 LRU?
LRU 全称 Least Recently Used(最近最少使用)。它是一种缓存替换策略,解决的正是上述“谁该被淘汰”的问题。
**
**
关键思想:
- 每当一个数据被访问,就将其标记为“最新使用”
- 当缓存空间不足时,淘汰那个最久没被访问的数据
三、与内存管理的关系:操作系统就是 LRU 的大本营
操作系统中的页替换策略就是 LRU 的核心应用领域。
在分页管理中:
- 虚拟内存页:按需加载
- 物理页框:有限,超出就要淘汰
- 淘汰策略:可使用 LRU(或改进型,如 Clock)
四、如何用数据结构实现 LRU?
LRU 的需求:
- 快速访问:get(key) 要 O(1)
- 快速更新顺序:put(key, val) 也要 O(1)
- 淘汰“最久未用”的元素
最佳数据结构组合:
结构 | 用途 |
---|---|
哈希表 | O(1) 快速找到 key 对应节点 |
双向链表 | O(1) 移动、删除节点,维持访问顺序 |
链表逻辑:
- 最近访问的在链表头部
- 最久未访问的在链表尾部
- put/get 时,移动节点到头部
- 超过容量时,删除尾部节点
五、代码实现(C++ 示例,通俗易懂)
class LRUCache {
int cap;
list<pair<int, int>> cache; // 双向链表:存 <key, value>
unordered_map<int, list<pair<int, int>>::iterator> map; // key -> 节点位置
public:
LRUCache(int capacity) : cap(capacity) {}
int get(int key) {
if (map.find(key) == map.end()) return -1;
cache.splice(cache.begin(), cache, map[key]);
return map[key]->second;
}
void put(int key, int value) {
if (map.find(key) != map.end()) {
map[key]->second = value;
cache.splice(cache.begin(), cache, map[key]);
} else {
if (cache.size() == cap) {
int delKey = cache.back().first;
map.erase(delKey);
cache.pop_back();
}
cache.emplace_front(key, value);
map[key] = cache.begin();
}
}
};
六、LRU 给数据结构学习的启示
学习指引:
能力点 | 说明 |
---|---|
哈希表(unordered_map) | 快速定位元素 |
双向链表(list) | 快速删除 / 插入指定位置 |
容器组合思想 | 单一结构不够用,组合才是王道 |
操作系统的联系 | 桌面系统中实际使用策略,不只是刷题 |
七、工程实践中的 LRU:用得最多的策略之一
场景 | LRU 的作用 |
---|---|
Redis 缓存 | 淘汰旧键值对(默认策略之一) |
MySQL/InnoDB | 管理缓存页 buffer pool |
浏览器缓存 | 控制页面资源淘汰 |
嵌入式系统 | Flash 空间缓存控制 |
八、小结与延伸
LRU 给数据结构学习的启示:
- 数据结构不只是模板,更是为工程问题服务的工具
- 单一结构只能解决单一问题,组合才能对系统性问题
- 工程问题常常要求 O(1) + 空间最优,必须巧妙组合结构
- 比算法更重要的是“策略”与“思想”
延伸思考:
-
如果你还想继续优化 LRU,可以探索:
- LFU:频率淘汰
- LRU-K:时间窗口控制
- ARC:结合 LRU + LFU
更多资料
📖 推荐阅读:《Yocto项目实战教程:高效定制嵌入式Linux系统》
🎥 更多学习视频请关注 B 站:嵌入式Jerry