From 5e8c73c12c2ba637c18e9f79809769116fec4b15 Mon Sep 17 00:00:00 2001 From: Petrichor <390983386@qq.com> Date: Wed, 30 Mar 2022 15:39:21 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(lru):=20LRU=20=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=B7=98=E6=B1=B0=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cache New Get RemoveOldest Add Len --- .gitignore | 2 + .vscode/settings.json | 3 ++ go.mod | 3 ++ lru/lru.go | 88 +++++++++++++++++++++++++++++++++++++++++++ lru/lru_test.go | 55 +++++++++++++++++++++++++++ 5 files changed, 151 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 go.mod create mode 100644 lru/lru.go create mode 100644 lru/lru_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a70cd3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.jpg +*.png \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a460645 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "go.inferGopath": false +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1f7c52b --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module geecache + +go 1.18 diff --git a/lru/lru.go b/lru/lru.go new file mode 100644 index 0000000..4037638 --- /dev/null +++ b/lru/lru.go @@ -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() +} diff --git a/lru/lru_test.go b/lru/lru_test.go new file mode 100644 index 0000000..85d3e0b --- /dev/null +++ b/lru/lru_test.go @@ -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) + } +}