golang-lru Cache (一)LRU

简介

Golang 第三方库golang-lru基于双向链表实现了三种LRU及变种Cache:LRU,Q2,ARC。今天看了一下代码,简单优雅,整理一下笔记。

双向链表

双向链表在golang标准库container/list中实现。定义了两个核心结构体Element和List。

Element

双向链表的一个节点信息。

type Element struct {
    next, prev *Element     // 前向指针和后向指针
    list       *List        // 所属List
    Value      interface{}  // 节点值
}

// Next returns the next list element or nil.
func (e *Element) Next() *Element {
    if p := e.next; e.list != nil && p != &e.list.root {
        return p
    }
    return nil
}

// Prev returns the previous list element or nil.
func (e *Element) Prev() *Element {
    if p := e.prev; e.list != nil && p != &e.list.root {
        return p
    }
    return nil
}

List

List定义了双向链表的信息及核心方法。对应实现LRU而言,比较核心的方法包括:
- Init 初始化链表
- Len 链表长度
- MoveToFront 将指定元素移到表头
- Back 获取表尾元素
- lazyInit 使用链表前确保链表初始化
- PushFront 向链表头添加新元素
- Remove 将元素移除链表

链表操作不是线程安全的。

type List struct {
    root Element    // 链表root分别指向双向链表头和尾
    len  int
}

// insert inserts e after at, increments l.len, and returns e.
func (l *List) insert(e, at *Element) *Element {
    n := at.next
    at.next = e
    e.prev = at
    e.next = n
    n.prev = e
    e.list = l
    l.len++
    return e
}

// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
func (l *List) insertValue(v interface{}, at *Element) *Element {
    return l.insert(&Element{Value: v}, at)
}

// remove removes e from its list, decrements l.len, and returns e.
func (l *List) remove(e *Element) *Element {
    e.next.prev = e.prev
    e.prev.next = e.next
    e.next = nil
    e.prev = nil
    e.list = nil
    l.len--
    return e
}

// Remove removes e from l if e is an element of list l.
// It returns the element value e.Value.
// The element must not be nil.
func (l *List) Remove(e *Element) interface{} {
    if e.list == l {
        l.remove(e)
    }
    return e.Value
}

// PushFront inserts a new element e with value v at the front of list l and returns e.
func (l *List) PushFront(v interface{}) *Element {
    l.lazyInit()
    return l.insertValue(v, &l.root)
}

// MoveToFront moves element e to the front of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *List) MoveToFront(e *Element) {
    if e.list != l || l.root.next == e {
        return
    }
    l.insert(l.remove(e), &l.root)
}

simplelru

simplelru定义了基本的lru实现,接口文件定义在lru_interface.go中,一百多行代码就实现了基本功能。

type LRU struct {
    size      int
    evictList *list.List
    items     map[interface{}]*list.Element
    onEvict   EvictCallback
}

simplelru定义了LRU结构体,通过map判断cache是否存在,通过双向list实现数据在缓存中的时序。此时读写Cache实际就是map和list操作。

// Add adds a value to the cache.  Returns true if an eviction occurred.
func (c *LRU) Add(key, value interface{}) (evicted bool) {
    // 若数据已经在缓存中,将其移到队首,并返回结果
    if ent, ok := c.items[key]; ok {
        c.evictList.MoveToFront(ent)
        ent.Value.(*entry).value = value
        return false
    }

    // 若数据不在缓存中,将新记录添加到队首
    ent := &entry{key, value}
    entry := c.evictList.PushFront(ent)
    c.items[key] = entry

    // 若缓存超长,清理队尾缓存数据
    evict := c.evictList.Len() > c.size
    // Verify size not exceeded
    if evict {
        c.removeOldest()
    }
    return evict
}

由于map和list都是非线程安全,所以simplelru也是非线程安全的。

LRU

要实现线程安全的LRU只需要在simplelru上加一个锁。

type Cache struct {
    lru  simplelru.LRUCache
    lock sync.RWMutex
}

func (c *Cache) Add(key, value interface{}) (evicted bool) {
    c.lock.Lock()
    defer c.lock.Unlock()
    return c.lru.Add(key, value)
}

总结

每个模块一百多行代码,很简单就实现了标准LRU,关于标准LRU存在的问题及优化在后面的文章中介绍。

你可能感兴趣的:(golang)