LRU 演算法

lidashuang發表於2019-02-16

LRU 最近最少使用演算法,LRU演算法主要用於快取淘汰。主要目的就是把最近最少使用的資料移除記憶體,以載入其他資料。

原理

新增元素時,放到連結串列頭
快取命中,將元素移動到連結串列頭
快取滿了之後,將連結串列尾的元素刪除

LRU演算法實現

  • 可以用一個雙向連結串列儲存資料
  • 使用hash實現O(1)的訪問

groupcache中LRU演算法實現(Go語言)
https://github.com/golang/groupcache/blob/master/lru/lru.go

原始碼簡單註釋:

package lru

import "container/list"

// Cache 結構體,定義lru cache 不是執行緒安全的
type Cache struct {
    // 數目限制,0是無限制 
    MaxEntries int

    // 刪除時, 可以新增可選的回撥函式
    OnEvicted func(key Key, value interface{})

    ll    *list.List // 使用連結串列儲存資料
    cache map[interface{}]*list.Element  // map 
}

// Key 是任何可以比較的值  http://golang.org/ref/spec#Comparison_operators
type Key interface{}

type entry struct {
    key   Key
    value interface{}
}

// 建立新的cache 物件
func New(maxEntries int) *Cache {
    return &Cache{
        MaxEntries: maxEntries,
        ll:         list.New(),
        cache:      make(map[interface{}]*list.Element),
    }
}

// 新增新的值到cache裡
func (c *Cache) Add(key Key, value interface{}) {
    if c.cache == nil {
        c.cache = make(map[interface{}]*list.Element)
        c.ll = list.New()
    }
    if ee, ok := c.cache[key]; ok {
                // 快取命中移動到連結串列的頭部
        c.ll.MoveToFront(ee)
        ee.Value.(*entry).value = value
        return
    }
        // 新增資料到連結串列頭部
    ele := c.ll.PushFront(&entry{key, value})
    c.cache[key] = ele
    if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
        // 滿了刪除最後訪問的元素
        c.RemoveOldest()
    }
}

// 從cache裡獲取值.
func (c *Cache) Get(key Key) (value interface{}, ok bool) {
    if c.cache == nil {
        return
    }
    if ele, hit := c.cache[key]; hit {
        // 快取命中,將命中元素移動到連結串列頭
        c.ll.MoveToFront(ele)
        return ele.Value.(*entry).value, true
    }
    return
}

// 刪除指定key的元素
func (c *Cache) Remove(key Key) {
    if c.cache == nil {
        return
    }
    if ele, hit := c.cache[key]; hit {
        c.removeElement(ele)
    }
}

// 刪除最後訪問的元素
func (c *Cache) RemoveOldest() {
    if c.cache == nil {
        return
    }
    ele := c.ll.Back()
    if ele != nil {
        c.removeElement(ele)
    }
}

func (c *Cache) removeElement(e *list.Element) {
    c.ll.Remove(e)
    kv := e.Value.(*entry)
    delete(c.cache, kv.key)
    if c.OnEvicted != nil {
        c.OnEvicted(kv.key, kv.value)
    }
}

// cache 快取數
func (c *Cache) Len() int {
    if c.cache == nil {
        return 0
    }
    return c.ll.Len()
}

相關文章