golang-lru源码解析(1)项目结构、simplelru

文章目录

  • 项目结构
  • LRUCache接口
  • simplelru
    • entry结构
    • LRU结构
    • 提供的方法
      • 构造方法
      • Purge方法
      • Add方法
      • Get方法
      • Contains方法
      • Peek方法
      • Remove方法
      • RemoveOldest方法
      • GetOldest方法
      • Keys方法
      • Resize方法
    • 总结

项目git地址点我
golang-lru提供了三个模块。LRU,2Q,ARC三个模块。本文主要介绍整个包的结构,以及为三个模块提供最底层功能的simplelru。

项目结构

golang-lru源码解析(1)项目结构、simplelru_第1张图片
整个项目结构非常简单,对外提供三个平行的模块在各自的文件中。simplelru提供了底层的simplelru结构,并且提供了一个lru接口。

LRUCache接口

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
}

simplelru

entry结构

节点元素就是简单的kv结构

type entry struct {
    key interface{}
    value interface{}
}

LRU结构

典型的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方法

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()
}

Add方法

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
}

Get方法

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
}

Contains方法

直接在map中查找是否存在即可

func (c *LRU) Contains(key interface{}) (ok bool) {
    _, ok = c.items[key]
    return ok
}

Peek方法

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方法

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)
    }
}

RemoveOldest方法

直接移除最旧元素的方法。这里从链表队尾取出元素后进行删除。链表中的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方法

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方法

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方法

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缓存》

你可能感兴趣的:(源码学习,golang)