Skip to content

Commit

Permalink
✨ feat(lru): LRU 缓存淘汰策略
Browse files Browse the repository at this point in the history
Cache New Get RemoveOldest Add Len
  • Loading branch information
p3ddd committed Mar 30, 2022
0 parents commit 5e8c73c
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.jpg
*.png
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"go.inferGopath": false
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module geecache

go 1.18
88 changes: 88 additions & 0 deletions lru/lru.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package lru

import (
"container/list"
)

// 核心数据结构
// map + double linked list

// Cache is a LRU cache. Not safe for ocncurrent access.
type Cache struct {
maxBytes int64 // 允许的最大内存
nbytes int64 // 已使用内存
ll *list.List // 双向链表
cache map[string]*list.Element
OnEvicted func(key string, value Value) // 某条记录被移除时的回调,可以为 nil
}

type entry struct {
key string
value Value
}

type Value interface {
Len() int
}

// New 实例化
func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
return &Cache{
maxBytes: maxBytes,
ll: list.New(),
cache: make(map[string]*list.Element),
OnEvicted: onEvicted,
}
}

// Get 查找
//
// 如果键对应的链表结点存在,将对应节点移动到队首,并返回找到的值
func (c *Cache) Get(key string) (value Value, ok bool) {
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
return kv.value, true
}
return
}

// RemoveOldest 缓存淘汰
//
// 移除最近最少访问的节点(尾节点)
func (c *Cache) RemoveOldest() {
ele := c.ll.Back()
if ele != nil {
c.ll.Remove(ele)
kv := ele.Value.(*entry)
//TODO
delete(c.cache, kv.key)
c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
if c.OnEvicted != nil {
c.OnEvicted(kv.key, kv.value)
}
}
}

// Add 添加一个值到缓存中
func (c *Cache) Add(key string, value Value) {
// 已存在,修改值,并将该节点移动到队首
if ele, ok := c.cache[key]; ok {
c.ll.MoveToFront(ele)
kv := ele.Value.(*entry)
c.nbytes += int64(value.Len()) - int64(kv.value.Len())
kv.value = value
} else {
ele := c.ll.PushFront(&entry{key, value})
c.cache[key] = ele
c.nbytes += int64(len(key)) + int64(value.Len())
}
for c.maxBytes != 0 && c.maxBytes < c.nbytes {
c.RemoveOldest()
}
}

// Len 获取已添加数据条数
func (c *Cache) Len() int {
return c.ll.Len()
}
55 changes: 55 additions & 0 deletions lru/lru_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package lru

import (
"reflect"
"testing"
)

type String string

func (s String) Len() int {
return len(s)
}

func TestGet(t *testing.T) {
lru := New(int64(0), nil)
lru.Add("key1", String("1234"))
if v, ok := lru.Get("key1"); !ok || string(v.(String)) != "1234" {
t.Fatalf("cache hit key1=1234 failed")
}
if _, ok := lru.Get("key2"); ok {
t.Fatalf("cache miss key2 failed")
}
}

func TestRemoveOldest(t *testing.T) {
k1, k2, k3 := "key1", "key2", "k3"
v1, v2, v3 := "value1", "value2", "v3"
cap := len(k1 + k2 + v1 + v2)
lru := New(int64(cap), nil)
lru.Add(k1, String(v1))
lru.Add(k2, String(v2))
lru.Add(k3, String(v3))

if _, ok := lru.Get("key1"); ok || lru.Len() != 2 {
t.Fatalf("RemoveOldest key1 failed")
}
}

func TestOnEvicted(t *testing.T) {
keys := make([]string, 0)
callback := func(key string, value Value) {
keys = append(keys, key)
}
lru := New(int64(10), callback)
lru.Add("key1", String("123456"))
lru.Add("k2", String("k2"))
lru.Add("k3", String("k3"))
lru.Add("k4", String("k4"))

expect := []string{"key1", "k2"}

if !reflect.DeepEqual(expect, keys) {
t.Fatalf("Call OnEvicted failed, expect keys equals to %s", expect)
}
}

0 comments on commit 5e8c73c

Please sign in to comment.