关于 lru 和 lfu的简单实现
前言
lru和lfu是两种经典的内存淘汰算法,前者是淘汰最近最久未使用的空间 ,后者是淘汰最不经常使用 。乍一听感觉差不多,其实还是有些区别的。lru 关注的是时间先后,优先淘汰时间范围内最不经常用的空间 。 lfu 关注的是已使用的次数,优先淘汰那些使用次数最少的空间。
在实际的应用中,lru 要使用的多些,因为对于大部分情况,最近用到的,未来用到的概率比较大。 lfu 主要是应对lru 无法应对 那些以前的热点数据,但是由于某些原因最近不常用,而被淘汰的场景。
说的好,不如做得好。 下面对照 leecode 的两个题实现了两个简易版本。
一、最近最久未使用(LRU)

代码如下:
go
package main
import "container/list"
type LRUCacheNode struct {
Key int
Value int
}
type LRUCache struct {
LRUMap map[int]*list.Element
ValueList *list.List
Capacity int
}
func Constructor(capacity int) LRUCache {
return LRUCache{
LRUMap: make(map[int]*list.Element),
ValueList: list.New(),
Capacity: capacity,
}
}
func (this *LRUCache) getNode(key int) *LRUCacheNode {
elem, ok := this.LRUMap[key]
if !ok {
return nil
}
// visit key, move to front of list
this.ValueList.MoveToFront(elem)
elemNode := elem.Value.(*LRUCacheNode)
return elemNode
}
func (this *LRUCache) Get(key int) int {
node := this.getNode(key)
if node == nil {
return -1
}
return node.Value
}
func (this *LRUCache) Put(key int, value int) {
node := this.getNode(key)
if node != nil {
node.Value = value
return
}
// if full, delete the Least Recently Used node
if len(this.LRUMap) >= this.Capacity {
backElem := this.ValueList.Back()
this.ValueList.Remove(backElem)
backNode := backElem.Value.(*LRUCacheNode)
delete(this.LRUMap, backNode.Key)
}
newNode := &LRUCacheNode{
Key: key,
Value: value,
}
newElem := this.ValueList.PushFront(newNode)
this.LRUMap[key] = newElem
return
}
lru 关注的是时间维度,因此需要一个类似先进先出的list 来充当时间维度,把最近访问的放到前面,后面就是最久未使用的。
二、最不经常使用(LFU)
代码如下:
go
package main
import (
"container/list"
"fmt"
"sort"
"strings"
)
type LFUCache struct {
ValueMap map[int]*list.Element
FreMap map[int]*list.List
Capacity int
MinFre int
}
type LFUListNode struct {
Key int
Value int
CurFre int
}
func Constructor(capacity int) LFUCache {
return LFUCache{
ValueMap: make(map[int]*list.Element),
FreMap: make(map[int]*list.List),
Capacity: capacity,
MinFre: 0,
}
}
// get node from lfu cache
func (this *LFUCache) getNode(key int) *LFUListNode {
elem, ok := this.ValueMap[key]
if !ok {
return nil
}
lfuListNode, _ := elem.Value.(*LFUListNode)
curFre := lfuListNode.CurFre
curList, ok := this.FreMap[curFre]
if !ok {
panic("not found")
}
curList.Remove(elem)
newElem := this.addNodeToNextFre(lfuListNode)
// node: must set the newElem because elem is not equal to newElem
this.ValueMap[key] = newElem
// update min Fre curList is empty after move
if curList.Len() == 0 && curFre == this.MinFre {
this.updateMinFre()
}
return lfuListNode
}
func (this *LFUCache) updateMinFre() {
this.MinFre++
}
func (this *LFUCache) addNodeToNextFre(lfuListNode *LFUListNode) *list.Element {
lfuListNode.CurFre++
nextFreList, ok := this.FreMap[lfuListNode.CurFre]
if !ok {
nextFreList = list.New()
this.FreMap[lfuListNode.CurFre] = nextFreList
}
// add to the front of next minFreMap list
nextFreList.PushFront(lfuListNode)
return nextFreList.Front()
}
func (this *LFUCache) addNewElem(key int, value int) {
newLFUNode := &LFUListNode{
Key: key,
Value: value,
// new elem Fre is 1
CurFre: 1,
}
_, ok := this.FreMap[newLFUNode.CurFre]
if !ok {
this.FreMap[newLFUNode.CurFre] = list.New()
}
this.FreMap[newLFUNode.CurFre].PushFront(newLFUNode)
newElem := this.FreMap[newLFUNode.CurFre].Front()
this.ValueMap[key] = newElem
}
func (this *LFUCache) Get(key int) int {
lfuListNode := this.getNode(key)
if lfuListNode == nil {
return -1
}
return lfuListNode.Value
}
func (this *LFUCache) Put(key int, value int) {
lfuNode := this.getNode(key)
if lfuNode != nil {
lfuNode.Value = value
return
}
// already full, delete one elem
if len(this.ValueMap) >= this.Capacity {
minFreList, minFreOk := this.FreMap[this.MinFre]
if !minFreOk {
panic("not found")
}
exitElem := minFreList.Back()
lfuNode := exitElem.Value.(*LFUListNode)
minFreList.Remove(exitElem)
delete(this.ValueMap, lfuNode.Key)
}
this.addNewElem(key, value)
// min fre is 1, because we add a new elem
this.MinFre = 1
return
}
lfu 算法要稍微难点,因为要记住每个 key 对应的使用频率,这是一个 map ,frequency对应的list,因为同一个频率会有多个 key。 同时还要维护一个最小频率,因为在删除的时候要优先从最小频率对应的那个 list 中删除最后一个元素。
参考
【1】LRU
【2】LFU
