golang-lru Cache (一)LRU
阿新 • • 發佈:2019-02-08
簡介
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
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存在的問題及優化在後面的文章中介紹。