go實現LRU cache

1. LRU簡介

1.1 概述

緩存資源一般比較昂貴,一般數據量較大時,會竟可能從較少的緩存知足儘量多訪問,這裏有一種假設,一般最近被訪問的數據,那麼它就有可能會被後續繼續訪問,基於這種假設,將全部的數據按訪問時間進行排序,並按驅逐出舊數據,那麼存在緩存的數據就爲熱點數據,這樣既節省了內存資源,又極大的知足了訪問.LRU(Least recently used)算法就是基於這種假設的一直緩存置換算法.git

1.2 算法流程

圖片描述

假設緩存大小爲4,而寫入順序爲A B C D E D F.訪問順序分爲寫入以及讀取兩種操做,寫入須要更新訪問時間,而且當數據到達最大緩存時須要逐出數據,而讀取只會更新訪問時間,寫入置換算法流程如上圖所示.github

當未到達緩存大小時,全部數據按寫入存儲,並記錄寫入次序.
寫入E時緩存已經滿,且E的值不存在,須要逐出最久未訪問的數據A,此時緩存內容爲E D C B.
下一個寫入D, D在緩存中,直接更新D的訪問次序,此時緩存內容爲 D E C B
下一個寫入F, F不在緩存中,逐出緩存中的末尾C,此時緩存內容爲 F D E C 算法

2 go實現

2.1思路

採用go,能夠使用list加map實現LRU cache,具體思路爲:
寫入時,先從map中查詢,若是能查詢,若是能查詢到值,則將該值的在List中移動到最前面.若是查詢不到值,則判斷當前map是否到達最大值,若是到達最大值則移除List最後面的值,同時刪除map中的值,若是map容量未達最大值,則寫入map,同時將值放在List最前面.緩存

讀取時,從map中查詢,若是能查詢到值,則直接將List中該值移動到最前面,返回查詢結果.安全

爲保證併發安全,須要引入讀寫鎖.
另外,存在讀取List中內容反差map的狀況,由於聲明一個容器對象同時保存key以及value, List中以及map中存儲的都是容器對象的引用.
引入原子對象對命中數以及未命中數等指標進行統計併發

2.2 關鍵代碼

完整代碼見: https://github.com/g4zhuj/cachespa

  • Set(寫入操做)
func (c *MemCache) Set(key string, value interface{}) {
    c.mutex.Lock()
    defer c.mutex.Unlock()
    if c.cache == nil {
        c.cache = make(map[interface{}]*list.Element)
        c.cacheList = list.New()
    }

    //判斷是否在map中,若是在map中,則將value從list中移動到前面.
    if ele, ok := c.cache[key]; ok {
        c.cacheList.MoveToFront(ele)
        ele.Value.(*entry).value = value
        return
    }

    //若是再也不map中,將值存到List最前面
    ele := c.cacheList.PushFront(&entry{key: key, value: value})
    c.cache[key] = ele
    //判斷是否到達容量限制,到達容量限制時刪除List中最後面的值.
    if c.maxItemSize != 0 && c.cacheList.Len() > c.maxItemSize {
        c.RemoveOldest()
    }
}
  • Get(讀取操做)
func (c *MemCache) Get(key string) (interface{}, bool) {
    c.mutex.RLock()
    defer c.mutex.RUnlock()
    c.gets.Add(1)
    //若是讀取到值,移動在List中位置,並返回value
    if ele, hit := c.cache[key]; hit {
        c.hits.Add(1)
        c.cacheList.MoveToFront(ele)
        return ele.Value.(*entry).value, true
    }
    return nil, false
}

3. 參考

https://en.wikipedia.org/wiki...code

相關文章
相關標籤/搜索