下面分 四层 来讲:
⸻
一、LRUCache 的"原理"到底是什么?
1️⃣ 先把名字翻译成人话
LRU = Least Recently Used
如果东西太多放不下,就扔掉"最久没用过的"
它不是学术概念,而是非常人类的直觉行为:
• 浏览器缓存
• 手机后台
• 数据库缓存
• CDN
• Redis
• 操作系统页缓存
你每天都在用 LRU,只是没意识到。
⸻
2️⃣ LRUCache 解决的是一个非常具体的问题
如何在"有限内存"下,让"最可能再被用到的数据"留得更久?
现实约束:
• 内存有限(不能无限存)
• 访问有"局部性规律"
• 最近用过的东西,大概率马上还会用
这叫 时间局部性(Temporal Locality)
⸻
二、为什么"最近使用"是一个好策略?
对比几种"删除策略":
策略 删除谁 问题
FIFO 最早进来的 不管用没用过
Random 随机删 命中率不稳定
LFU 用得最少 维护成本高
LRU 最久没用过 效果好 + 成本低
LRU 是一个:
• 成本可控
• 行为符合直觉
• 命中率高
👉 工程上的"性价比之王"
⸻
三、LRUCache 的核心原理(抽象层)
LRU 本质只做三件事:
- 快速判断 key 在不在缓存
- 每次访问后,标记为"最近使用"
- 超容量时,删掉"最久没使用"的
这三点 缺一不可。
⸻
四、为什么一定要「dict + 双向链表」?
问题拆解:
1️⃣ 我要 O(1) 查找
👉 用 dict
2️⃣ 我要 O(1) 移动"使用顺序"
👉 用 双向链表
3️⃣ 我要 O(1) 删除最旧的
👉 链表头直接删
4️⃣ 我要 O(1) 找到链表节点
👉 dict[key] → node
这就是为什么结构一定是:
hash map + doubly linked list
不是"巧合",是唯一解之一。
⸻
五、LRUCache 的「工程级结构示意」
dict:
key → Node地址
双向链表:
HEAD\] \<-\> \[A\] \<-\> \[B\] \<-\> \[C\] \<-\> \[TAIL
最近使用:靠近 TAIL
最久未用:靠近 HEAD
get(key):
- dict 查 node
- node 从当前位置摘下
- node 插到 tail 前
put(key, value):
如果存在:
更新 value
移到 tail
如果不存在:
新建 node
插到 tail
dict[key] = node
如果超容量:
删除 head.next
⸻
六、为什么面试官一定爱考 LRUCache?
这点非常关键。
这道题 不是在考你会不会 LRU
而是在一题里同时考 6 件事:
⸻
七、面试官真正想看的能力(重点)
① 你会不会拆需求
"get / put 都是 O(1)"
这是一个强约束
如果你忽略这个条件,直接 list + dict → ❌
⸻
② 你知不知道基础数据结构
• hash table
• doubly linked list
不是 API,而是 结构思维
⸻
③ 你有没有工程意识
LRU 是:
• 真实存在
• 真正在用
• 不是刷题产物
⸻
④ 你会不会组合结构
面试官最爱的一点
不是:
• "背一个结构"
而是:
知道一个结构不够 → 组合两个
⸻
⑤ 你对「复杂度」是不是敏感
很多人写得出来
但解释不了:
"为什么这是 O(1)"
你现在问的正是这个点。
⸻
⑥ 你能不能解释**"为什么这样设计"**
能写 ≠ 能讲
能讲 ≠ 懂
你现在在往「懂」走。
⸻
八、这题在真实世界里"解决了什么"?
举几个真实系统例子:
🔹 浏览器缓存
• 最近访问的页面资源
• 最久不用的图片被清掉
🔹 Redis
• 内存满了
• LRU / LFU 淘汰 key
🔹 数据库
• 查询结果缓存
• 热数据常驻
🔹 操作系统
• 页缓存
• 文件缓存
👉 不是"算法题",是"系统题"
⸻
九、面试官最想听的一句话(精华)
你可以直接背:
LRUCache 用来在有限内存下提高缓存命中率,
利用时间局部性原理,
通过哈希表保证 O(1) 查找,
通过双向链表维护访问顺序,
在容量受限时淘汰最久未被访问的数据。