1. 程式人生 > >golang-lru Cache (一)LRU

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

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存在的問題及優化在後面的文章中介紹。