Golang 第三方库golang-lru基于双向链表实现了三种LRU及变种Cache:LRU,Q2,ARC。今天看了一下代码,简单优雅,整理一下笔记。
双向链表在golang标准库container/list中实现。定义了两个核心结构体Element和List。
双向链表的一个节点信息。
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定义了双向链表的信息及核心方法。对应实现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定义了基本的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只需要在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存在的问题及优化在后面的文章中介绍。