ordereddict 是实现 lru 缓存最简可靠的起点,因其天然维护访问顺序,move_to_end() 和 popitem(last=false) 均为 o(1),避免了 list 遍历删除的 o(n) 开销及额外时间戳字典的同步问题。

functools.lru_cache 能解决大部分函数级缓存需求,但真要控制容量、手动管理键值、支持复杂淘汰逻辑(比如带权重或过期时间),就得自己实现 —— 而且不能只靠 dict + list 模拟。
为什么 OrderedDict 是最简可靠的起点
OrderedDict 天然维护插入/访问顺序,move_to_end() 和 popitem(last=False) 两步就能完成“访问更新”和“淘汰最老项”,时间复杂度都是 O(1)。比手写双链表少出错,比用 list 索引删除快得多(后者删头是 O(n))。
- 不要用
list存键顺序:每次 get 都得遍历找位置,put 时删头还要pop(0),性能崩坏 - 别在
dict外额外维护时间戳字典:增加同步负担,容易不一致 -
OrderedDict在 Python 3.7+ 已保证插入顺序,但move_to_end()行为仍是它独有的关键能力
自己写 LRU 时必须处理的三个边界
- 缓存未命中时,put() 插入新 key 前要先判断是否已达 capacity;满则先 popitem(last=False),再赋值
- get() 命中后必须调用 move_to_end(key),否则顺序错乱,淘汰逻辑失效
- 容量为 0 时,所有 put() 应直接丢弃,get() 全部返回 None 或抛异常,不能让 OrderedDict 积累无效数据
from collections import OrderedDict
<p>class LRUCache:
def <strong>init</strong>(self, capacity: int):
self.capacity = capacity
self.od = OrderedDict()</p><pre class="brush:python;toolbar:false;">def get(self, key):
if key not in self.od:
return None
self.od.move_to_end(key) # 关键:刷新访问序
return self.od[key]
def put(self, key, value):
if key in self.od:
self.od.move_to_end(key)
elif len(self.od) >= self.capacity > 0:
self.od.popitem(last=False) # 淘汰最久未用
if self.capacity > 0:
self.od[key] = value
什么时候该放弃 OrderedDict 改用手写双向链表
- 需要精确控制内存布局(如嵌入式或超低延迟场景) - 要支持并发读写,而OrderedDict 本身非线程安全,加锁又影响吞吐
- 淘汰策略升级为 LRU-K、SLRU 或带 TTL,单靠访问序不够,得存多维状态(例如最近 K 次时间戳、引用计数)
这时每个节点必须同时持有 key、value、prev、next,哈希表映射 key → ListNode,所有操作围绕指针展开。但日常业务里,95% 的场景 OrderedDict 就够用 —— 过早优化反而让代码难 debug、难测试。
真正容易被忽略的不是结构选型,而是容量变更:运行时动态调大/缩小 capacity 会破坏现有顺序一致性,要么清空重置,要么逐个检查并裁剪,没有银弹。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











