Go 简单实现 LRU 缓存
Feb 4, 2022 22:00 · 744 words · 2 minute read
LRU(Least recently used)——优先淘汰最久未被使用访问过的数据。
思路:缓存通常以 HashMap 来提供访问接口,保证常量复杂度的读取性能;以 LinkedList 的节点顺序来表示数据的时间顺序,在每次命中缓存时,将返回的对象调整到链表头;缓存淘汰时从链表尾清理数据。
import "container/list"
type Key interface{}
type Cache struct {
ll *list.List
cache map[Key]*list.Element
}
func New() *Cache {
return &Cache{
ll: list.New(),
cache: make(map[Key]*list.Element),
}
}
实现 Add 方法
- 如果 HashMap 中 key 存在,将链表中的对象移动到表头,并更新 HashMap
- 如果 HashMap 中 key 不存在,将值作为对象添加到链表头,并键值对插入 HashMap
func (c *Cache) Add(key Key, value interface{}) {
if c.cache == nil {
c.ll = list.New()
c.cache = make(map[Key]*list.Element)
}
if ee, ok := c.cache[key]; ok {
c.ll.MoveToFront(ee)
ee.Value = value
return
}
ele := c.ll.PushFront(value)
c.cache[key] = ele
}
实现 Del 方法
- 如果 HashMap 中 key 存在,删除链表中对应的值对象,并删除 HashMap 中的键值对
func (c *Cache) Del(key Key) {
if c.cache == nil {
return
}
if ele, ok := c.cache[key]; ok {
c.ll.Remove(ele)
delete(c.cache, key)
}
}
实现 Get 方法
- 如果 HashMap 中 key 存在,将链表中对应的值对象移动到表头并返回该值
func (c *Cache) Get(key Key) (value interface{}, ok bool) {
if c.cache == nil {
return
}
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
return ele.Value, true
}
return
}
实现 RemoveOldest 方法
- 获取链表尾的元素,并删除 HashMap 中的键值对
func (c *Cache) RemoveOldest() {
if c.cache == nil {
return
}
ele := c.ll.Back()
c.ll.Remove(ele)
// delete(c.cache) @_@
}
那么问题来了,以上实现,无法通过链表中的值对象得知其对应的 key。。。
所以我们在维护链表时,可以将键值对结构化为一个对象作为元素:
type entry struct {
key Key
value interface{}
}
重新实现 Add 方法
func (c *Cache) Add(key Key, value interface{}) {
if c.cache == nil {
c.ll = list.New()
c.cache = make(map[Key]*list.Element)
}
if ee, ok := c.cache[key]; ok {
c.ll.MoveToFront(ee)
ee.Value.(*entry).value = value
return
}
ele := c.ll.PushFront(&entry{key, value})
c.cache[key] = ele
}
重新实现 Del 方法
func (c *Cache) Del(key Key) {
if c.cache == nil {
return
}
if ele, ok := c.cache[key]; ok {
c.ll.Remove(ele)
kv := ele.Value.(*entry)
delete(c.cache, kv.key)
}
}
重新实现 Get 方法
func (c *Cache) Get(key Key) (value interface{}, ok bool) {
if c.cache == nil {
return
}
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
return ele.Value.(*entry).value, true
}
return
}
重新实现 RemoveOldest 方法
通过 entry 结构体就可以拿到值对应的 key,从而删除 HashMap 中的键值对:
func (c *Cache) RemoveOldest() {
if c.cache == nil {
return
}
ele := c.ll.Back()
c.ll.Remove(ele)
kv := ele.Value.(*entry)
delete(c.cache, kv.key)
}
以上来自于 Golang 官方的 groupcache 项目中的 lru 包,更为详细的实现请查看源码:https://github.com/golang/groupcache/blob/master/lru/lru.go