LFU演算法實現

落雷發表於2024-07-07

LFU (Least Frequently Used) 是一種用於快取管理的演算法。它透過跟蹤每個快取項被訪問的頻率來決定哪些項應該被移除。LFU演算法傾向於保留那些使用頻率較高的項,而移除那些使用頻率較低的項。以下是LFU演算法的詳細介紹:

工作原理

  1. 計數器:每個快取項都有一個計數器,用於記錄該項被訪問的次數。
  2. 增加計數:每次快取項被訪問時,其計數器加一。
  3. 移除策略:當快取滿時,移除計數器值最小的項。如果有多個項的計數器值相同,則根據預定規則(如最早被訪問的項)移除其中一個。

實現

LFU演算法的實現可以使用多種資料結構,如雜湊表、雙向連結串列和優先佇列。以下是一種常見的實現方法:

使用雜湊表和優先佇列

  1. 雜湊表 (cache):用於儲存快取項及其計數器。
  2. 優先佇列 (min-heap):用於快速找到計數器值最小的項。

具體步驟如下:

  1. 插入/更新快取項

    • 如果快取項已存在,更新其計數器並調整優先佇列中的位置。
    • 如果快取項不存在,檢查快取是否已滿。如果已滿,移除優先佇列中計數器值最小的項,然後插入新項。
  2. 訪問快取項

    • 如果快取項存在,更新其計數器並調整優先佇列中的位置。
    • 如果快取項不存在,返回未命中。

應用場景

LFU演算法適用於以下場景:

  • 資料訪問具有明顯的熱點資料,且熱點資料相對穩定。
  • 需要高效管理快取資源,減少快取未命中率。

Go實現

package lfu

import (
	"container/list"
	"sync"
)

type entry struct {
	key   any
	value any
	freq  int
}

type LFUCache struct {
	mtx       sync.Mutex // protects the cache
	capacity  int
	size      int
	minFreq   int
	cache     map[any]*list.Element
	frequency map[int]*list.List
}

// NewLFUCache creates a new LFU cache
func NewLFUCache(capacity int) *LFUCache {
	return &LFUCache{
		capacity:  capacity,
		cache:     make(map[any]*list.Element),
		frequency: make(map[int]*list.List),
	}
}

// Get retrieves a value from the cache
func (c *LFUCache) Get(key any) any {
	c.mtx.Lock()
	defer c.mtx.Unlock()
	if elem, found := c.cache[key]; found {
		c.incrementFrequency(elem)
		return elem.Value.(*entry).value
	}
	return nil
}

// Put inserts or updates a value in the cache
func (c *LFUCache) Put(key, value any) {
	c.mtx.Lock()
	defer c.mtx.Unlock()

	if c.capacity == 0 {
		return
	}

	if elem, found := c.cache[key]; found {
		elem.Value.(*entry).value = value
		c.incrementFrequency(elem)
	} else {
		if c.size == c.capacity {
			c.evict()
		}
		newEntry := &entry{key, value, 1}
		if c.frequency[1] == nil {
			c.frequency[1] = list.New()
		}
		elem := c.frequency[1].PushFront(newEntry)
		c.cache[key] = elem
		c.minFreq = 1
		c.size++
	}
}

// incrementFrequency increases the frequency of a cache entry
func (c *LFUCache) incrementFrequency(elem *list.Element) {
	e := elem.Value.(*entry)
	oldFreq := e.freq
	e.freq++

	c.frequency[oldFreq].Remove(elem)
	if c.frequency[oldFreq].Len() == 0 {
		delete(c.frequency, oldFreq)
		if c.minFreq == oldFreq {
			c.minFreq++
		}
	}

	if c.frequency[e.freq] == nil {
		c.frequency[e.freq] = list.New()
	}
	newElem := c.frequency[e.freq].PushFront(e)
    c.cache[e.key] = newElem
}

// evict removes the least frequently used cache entry
func (c *LFUCache) evict() {
	list := c.frequency[c.minFreq]
	elem := list.Back()
	if elem != nil {
		list.Remove(elem)
		delete(c.cache, elem.Value.(*entry).key)
		c.size--
	}
}

孟斯特

宣告:本作品採用署名-非商業性使用-相同方式共享 4.0 國際 (CC BY-NC-SA 4.0)進行許可,使用時請註明出處。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 戀水無意
騰訊雲開發者社群:孟斯特


相關文章