整个项目结构非常简单,对外提供三个平行的模块在各自的文件中。simplelru提供了底层的simplelru结构,并且提供了一个lru接口。
LRUCache定义了lru缓存的方法。这里注释给的比较详细,直接照搬了。
// LRUCache is the interface for simple LRU cache.
type LRUCache interface {
// Adds a value to the cache, returns true if an eviction occurred and
// updates the "recently used"-ness of the key.
Add(key, value interface{}) bool
// Returns key's value from the cache and
// updates the "recently used"-ness of the key. #value, isFound
Get(key interface{}) (value interface{}, ok bool)
// Checks if a key exists in cache without updating the recent-ness.
Contains(key interface{}) (ok bool)
// Returns key's value without updating the "recently used"-ness of the key.
// 这里额外提供的Peek方法用来根据key获取value,并且不更新这个元素在队列中的信息
Peek(key interface{}) (value interface{}, ok bool)
// Removes a key from the cache.
Remove(key interface{}) bool
// Removes the oldest entry from cache.
RemoveOldest() (interface{}, interface{}, bool)
// Returns the oldest entry from the cache. #key, value, isFound
GetOldest() (interface{}, interface{}, bool)
// Returns a slice of the keys in the cache, from oldest to newest.
Keys() []interface{}
// Returns the number of items in the cache.
Len() int
// Clears all cache entries.
Purge()
// Resizes cache, returning number evicted
Resize(int) int
}
节点元素就是简单的kv结构
type entry struct {
key interface{}
value interface{}
}
典型的LRU结构,通过list + map的方式进行存储,链表用来给队列排序,方便淘汰队尾的元素,map结构保证激活元素的时候查询的复杂度为O(N)。还添加了一个回调方法当元素离开队列后被调用
type EvictCallback func(key interface{}, value interface{})
type LRU struct {
size int // 整个缓存可以存放的元素数量
evictList *list.list // 存储元素的列表,存储的主要结构
items map[interface{}]*list.Element // map结构,value为列表中元素的指针,减少存储空间,搜索的主要结构
onEvict EvictCallback
}
func NewLRU(size int, onEvict EvictCallback) (*LRU, error) {
// 参数检查
if size <= 0 {
return nil, errors.New("Must provide a positive size")
}
// 成员初始化
c := &LRU{
size: size,
evictList: list.New(),
items: make(map[interface{}]*list.Element),
onEvict: onEvict,
}
return c, nil
}
purge方法清理整个LRU中所有的元素
func (c *LRU) Purge() {
for k, v := range c.items{
// 调用回调方法。
// 这里map中的value是go list中的element,真正的value还要从element中取出entry,再取value
if c.onEvict != nil {
c.onEvict(k, v.Value.(*entry).value)
}
// 从map中删除元素
delete(c.items, k)
}
// 链表直接清空
c.evictList.Init()
}
func (c *LRU) Add(key, value interface{}) (evicted bool) {
// 先判断元素是否已经存在
if ent, ok := c.items[key]; ok {
// 如果存在则更新这个元素的value,并移到队首
// go list提供的api:https://studygolang.com/static/pkgdoc/pkg/container_list.htm
c.evictList.MoveToFront(ent)
ent.Value.(*entry).value = value
// key已存在,没有新增元素
return false
}
// 添加新元素
ent := &entry{key, value}
entry := c.evictList.PushFront(ent)
c.items[key] = entry
// 判断元素数量是否超过最大长度
evict := c.evictList.Len() > c.size
if evict {
c.removeOldest()
}
return evict
}
func (c *LRU) Get(key interface{}) (value interface{}, ok bool) {
// Get相比普通map,主要就是在链表中更新元素到队首
if ent, ok := c.items[key]; ok {
c.evictList.MoveToFront(ent)
if ent.Value.(*entry) == nil {
return nil, false
}
return ent.Value.(*entry).value, true
}
return
}
直接在map中查找是否存在即可
func (c *LRU) Contains(key interface{}) (ok bool) {
_, ok = c.items[key]
return ok
}
peek提供了一个查找元素value,并且不对链表进行更新的方法。相当于直接从map中取元素
func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) {
var ent *list.Element
if ent, ok = c.items[key]; ok {
return ent.Value.(*entry).value, true
}
return nil, ok
}
remove从LRU中移除一个元素。同样通过map查找元素地址,将时间复杂度提升到O(N)
如果存在回调,则对被移除的元素执行一次回调方法
func (c *LRU) Remove(key interface{}) (present bool) {
if ent, ok := c.items[key]; ok {
c.removeElement(ent)
return true
}
return false
}
func (c *LRU) removeElement(e *list.Element) {
c.evictList.Remove(e)
kv := e.Value.(*entry)
delete(c.items, kv.key)
if c.onEvict != nil {
c.onEvict(kv.key, kv.value)
}
}
直接移除最旧元素的方法。这里从链表队尾取出元素后进行删除。链表中的entry保存了kv,可以直接通过key在map中进行定位后进行删除。时间复杂度O(N)
func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) {
ent := c.evictList.Back()
if ent != nil {
c.removeElement(ent)
kv := ent.Value.(*entry)
return kv.key, kv.value, true
}
return nil, nil, false
}
Getoldest 直接从链表队尾获取元素的kv
func (c *LRU) GetOldest() (key interface{}, value interface{}, ok bool) {
ent := c.evictList.Back()
if ent != nil {
kv := ent.Value.(*entry)
return kv.key, kv.value, true
}
return nil, nil, false
}
keys方法以切片的方式返回整个缓存中的所有key
func (c *LRU) Keys() []interface{} {
keys := make([]interface{}, len(c.items))
i := 0
// 从链表队尾开始向前遍历,所以最后返回的切片的顺序是从老到新的
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
keys[i] = ent.Value.(*entry).key
i++
}
return keys
}
resize重新定义缓存的大小,返回移除掉的元素数量。如果新的大小小于当前元素的数量,则从队尾开始对多余的元素进行删除
func (c *LRU) Resize(size int) (evicted int) {
diff := c.Len() - size
if diff < 0 {
diff = 0
}
// 从队尾开始移除多余的元素
for i := 0; i < diff; i++ {
c.removeOldest()
}
// 更新size
c.size = size
return diff
}
func (c *LRU) removeOldest() {
ent := c.evictList.Back()
if ent != nil {
c.removeElement(ent)
}
}
simpleLRU提供了一个典型的LRU的实现,通过链表对元素进行存储,通过map对元素进行定位。用空间换时间将一些在链表中时间复杂度为O(N^2)的操作提升到O(N)
后续我们再来看如何使用多个基础的LRU结构在2Q和ARC中进行更加复杂的缓存逻辑。
《golang-lru源码解析(2)2q缓存》