问题背景
LRU(Least Recently Used),最近最少使用。根据时间维度来选择将要淘汰的元素,即删除掉最长时间没被访问的元素。
实现一个LRU算法需要解决的问题:
- 一个存储最近使用元素的结构,能够快速定位到元素(map)
- 如何计算元素被最少使用了,当添加或更新元素的时候方便查询最少使用元素(双向链表)
- 最近最少使用存在数量上限。(容量capacity)
实现原理
- 双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
- 哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置
删除节点
删除的时候链表和map中的都需要删除
新增节点
注意:
list
节点中也要存储Map
的key
技术内幕
代码路径:core/collection/cache.go
对象定义
1 2 3 4 5 6 7 8 9 10 11 12 13
| lru interface { add(key string) remove(key string) }
emptyLru struct{}
keyLru struct { limit int evicts *list.List elements map[string]*list.Element onEvict func(key string) //自定义触发函数 }
|
删除元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| func (klru *keyLru) remove(key string) { if elem, ok := klru.elements[key]; ok { klru.removeElement(elem) } }
func (klru *keyLru) removeOldest() { elem := klru.evicts.Back() if elem != nil { klru.removeElement(elem) } }
func (klru *keyLru) removeElement(e *list.Element) { klru.evicts.Remove(e) key := e.Value.(string) delete(klru.elements, key) klru.onEvict(key) }
|
添加元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func (klru *keyLru) add(key string) { if elem, ok := klru.elements[key]; ok { klru.evicts.MoveToFront(elem) return }
elem := klru.evicts.PushFront(key) klru.elements[key] = elem
if klru.evicts.Len() > klru.limit { klru.removeOldest() } }
|
参考链接
- https://talkgo.org/t/topic/2280