这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记
1 FIFO淘汰算法
FIFO(First In First Out,先进先出)淘汰算法是淘汰缓存中最早的记录。FIFO Cache的设计中,核心原则就是:如果一个数据最先进入缓存,那么也应该最早淘汰掉。这么认为的根据是,最早添加的记录,其不再被使用的可能性比刚添加的可能性大。
这种算法的实现非常简单,创建一个队列(一般通过双向链表实现),新增记录添加到队尾,缓存满了,淘汰队首。
2 FIFO算法实现
2.1 定义FIFO缓存结构体
type fifo struct {
// 最大容量
maxBytes int
// 当一个 entry 从缓存中移除是调用该回调函数,默认为 nil
onEvicted func(key string, value interface{})
// 已经使用的字节数,只包括值,key不算
usedBytes int
ll *list.List
cache map[string]*list.Element
}
该结构体中,核心的两个数据结构是 *list.List 和 map[string]*list.Element。list.List 中存放真实内容,map 的键是字符串,值是指向键所对应内容在 list.List 中的位置的指针。
- map 用来存储键值对。这是实现缓存最简单直接的数据结构。因为它的查找和增加时间复杂度都是 O(1)。
- list.List 是 Go 标准库提供的双向链表。通过这个数据结构存放具体的值,可以做到移动记录到队尾的时间复杂度是 O(1),在在队尾增加记录时间复杂度也是 O(1),同时删除一条记录时间复杂度同样是 O(1)。
2.2 定义指针
type entry struct {
key string
value interface{}
}
func (e *entry) Len() int {
return cache.CalcLen(e.value)
}
2.3 定义内存计算函数
这里考虑内存控制,只计算value占用的内存。
func CalcLen(value interface{}) int {
var n int
switch v := value.(type) {
case Value:
n = v.Len()
case string:
if runtime.GOARCH == "amd64" {
n = 16 + len(v)
} else {
n = n + len(v)
}
case bool, uint8, int8:
n = 1
case uint16, int16:
n = 2
case int32, uint32, float32:
n = 4
case int64, uint64, float64:
n = 8
case int, uint:
if runtime.GOARCH == "amd64" {
n = 8
} else {
n = 4
}
case complex64:
n = 8
case complex128:
n = 16
default:
panic(fmt.Sprintf("%T is not implement cache.Value", value))
}
return n
}
cache.Value 接口定义
type Value interface {
Len() int
}
2.4 定义缓存创建函数
func New(maxBytes int, onEvicted func(key string, value interface{})) *fifo {
return &fifo{
maxBytes: maxBytes,
onEvicted: onEvicted,
ll: list.New(),
cache: make(map[string]*list.Element),
}
}
2.5 定义功能函数
func (f *fifo) Set(key string, value interface{}) {
if e, ok := f.cache[key]; ok {
f.ll.MoveToBack(e)
en := e.Value.(*entry)
f.usedBytes = f.usedBytes - cache.CalcLen(en.value) + cache.CalcLen(value)
en.value = value
return
}
en := &entry{key, value}
e := f.ll.PushBack(en)
f.cache[key] = e
f.usedBytes += en.Len()
if f.maxBytes > 0 && f.usedBytes > f.maxBytes {
f.DelOldest()
}
}
func (f *fifo) Get(key string) interface{} {
if e, ok := f.cache[key]; ok {
return e.Value.(*entry).value
}
return nil
}
func (f *fifo) Del(key string) {
if e, ok := f.cache[key]; ok {
f.removeElement(e)
}
}
func (f *fifo) DelOldest() {
f.removeElement(f.ll.Front())
}
func (f *fifo) removeElement(e *list.Element) {
if e == nil {
return
}
f.ll.Remove(e)
en := e.Value.(*entry)
f.usedBytes -= en.Len()
delete(f.cache, en.key)
if f.onEvicted != nil {
f.onEvicted(en.key, en.value)
}
}
func (f *fifo) Len() int {
return f.ll.Len()
}