container/list+map是go实现lru最轻量可控方式,但必须组合使用:map实现o(1)查找,list维护访问顺序并支持o(1)移动与淘汰;单独用map无法识别最久未用项,单独用list查找为o(n);entry需含key字段以同步清理map,map必须显式make初始化,get中movetofront不可省略,加锁粒度要精准(get用rlock,put用lock),容量判断须用>=而非==,淘汰须先删map再删list。

container/list + map 是 Go 里实现 LRU 最轻量、最可控的方式,但直接照搬结构体定义和方法签名很容易在并发或容量边界上出错——不是逻辑写不对,而是删漏了 map 键、没加锁粒度、或历史访问时间没绑定到 entry 上。
为什么不能只用 map 或只用 list
单独用 map[string]interface{} 能 O(1) 查,但无法知道哪个 key “最久没被访问”;单独用 *list.List 能维护顺序,但查 key 得遍历,O(n) 不达标。必须组合:map 快查,list 快移位+淘汰。
常见错误是把 value 直接塞进 list.Element.Value,却不存对应 key 字段——这样 RemoveOldest 时根本不知道该从 map 里删哪个键,导致内存泄漏。
- entry 必须显式定义为
type entry struct { key string; value interface{} } -
map[string]*list.Element声明后,必须在构造函数里make(),否则cache[key] = elem直接 panic -
list.Element.Value存的是*entry,不是裸 value,否则淘汰时无法反查 key
Get 方法里 MoveToFront 为什么不能跳过
只查 map、取 value、返回值,看起来够了——但链表顺序没更新。下次 Put 触发淘汰时,刚被 Get 过的项可能因还在链表尾部而被误删。
正确流程必须是:查 map → 取 *list.Element → 在锁保护下调 list.MoveToFront(elem) → 再取 elem.Value 返回。
- 即使只是读操作,
MoveToFront也得做,这是 LRU 语义的核心 - 如果用了
sync.RWMutex,Get用RLock()即可,别升级成Lock() - 千万别在
MoveToFront后再改elem.Value却不重新MoveToFront——顺序就断了
容量控制失效的三个典型现场
LRU 最常崩在“满不满”的判断上,不是算法错,是边界条件漏了。
- 淘汰条件写成
if list.Len() == capacity:当capacity = 1,第一次Put后长度=1,第二次Put才触发淘汰,实际多存了一项 -
RemoveOldest里只调list.Remove(list.Back()),却忘了delete(cache, elem.Value.(*entry).key) -
Put时没检查list.Len() >= capacity就直接PushFront,导致链表超长,后续淘汰逻辑全乱
真正难的不是写完 Get 和 Put,是让每次 Put、Get、RemoveOldest 都严格同步更新 list 长度、map 键值、entry 内容——三者必须原子一致,差一步,缓存就不可信。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











