LRU Cache Interface or Template Go 代码实现
目录
1. LRU Cache
1.1 interface 版本
1.2 template 版本
2. [可选] 用例拓展
2.1 container/list.go
2.1.2 list 数据结构
2.1.2 list 使用例子
2.2 transport.go connLRU
正文
1. LRU Cache
LRU 是个经典通用功能设计场景, 通用思路用 map 类似的结构来存储数据源, 用双向链表存储新旧关系链条.
1.1 interface 版本
package cache import ( "container/list" "sync" ) type entry struct { key interface{} // container list 链接 map[interface{}]*list.Element value interface{} } // CacheLUR type CacheLRU struct { M sync.Mutex // 因为 Get 触发热度更新操作, 所以只能用互斥解决并发冲突 cap int // capacity 容量 <= 0 标识不限制 list *list.List // 双向链表 entry container list data map[interface{}]*list.Element // 数据池子 entry::key -> list::value } // NewCacheLRU new CacheLRU return struct pointer func NewCacheLRU(cap int) *CacheLRU { return &CacheLRU{ cap: cap, list: list.New(), data: make(map[interface{}]*list.Element), } } func (c *CacheLRU) Put(key, value interface{}) { element, ok := c.data[key] if ok { // 结点存在, 更新结点 element.Value.(*entry).value = value // 调整 element 热度 c.list.MoveToFront(element) return } // 容量不够走删除逻辑 if c.cap > 0 && c.cap <= c.list.Len() { delete(c.data, c.list.Remove(c.list.Back()).(*entry).key) } // 容量足够开始走添加操作 c.data[key] = c.list.PushFront(&entry{key: key, value: value}) } func (c *CacheLRU) Get(key interface{}) (value interface{}, ok bool) { element, ok := c.data[key] if ok { // 获取指定缓存和值 value = element.Value.(*entry).value // 调整 element 热度 c.list.MoveToFront(element) } return } func (c *CacheLRU) Delete(key interface{}) { element, ok := c.data[key] if ok { delete(c.data, key) c.list.Remove(element) } }
M sync.Mutex 互斥锁做线程安全, 需要就用; list 双向链表保存时间新老关系; map 可以让时间复杂度到 O(1).
使用教程:
// 创建 c := cache.New(1) // 设置 c.Put("123", "123") c.Put("234", "234") // 使用 fmt.Println(c.Get("123")) fmt.Println(c.Get("234")) // 删除 c.Delete("123")
1.2 template 版本
package cache import ( "container/list" "sync" ) type pair[K comparable, V any] struct { key K value V } // TemplateLRU go 1.18 对 LRU cache 包装 type TemplateLRU[K comparable, V any] struct { M sync.Mutex // 如果需要并发安全, 所有操作可以通过 M 加锁 cap int // capacity 容量 <= 0 标识不限制 list *list.List // entry container list data map[K]*list.Element // 数据池子 } // NewTemplateLRU new cache LRU func NewTemplateLRU[K comparable, V any](cap int) *TemplateLRU[K, V] { return &TemplateLRU[K, V]{ cap: cap, list: list.New(), data: make(map[K]*list.Element), } } // Get 获取 value, ok is true 标识存在 func (c *TemplateLRU[K, V]) Get(key K) (value V, ok bool) { element, ok := c.data[key] if ok { value = element.Value.(*pair[K, V]).value c.list.MoveToFront(element) } return } // Delete 通过 key 删除操作 func (c *TemplateLRU[K, V]) Delete(key K) { element, ok := c.data[key] if ok { delete(c.data, key) c.list.Remove(element) } } // Put 添加数据 func (c *TemplateLRU[K, V]) Put(key K, value V) { element, ok := c.data[key] if ok { element.Value.(*pair[K, V]).value = value c.list.MoveToFront(element) return } // 容量不足, 尝试清理数据 if c.cap > 0 && c.cap <= c.list.Len() { delete(c.data, c.list.Remove(c.list.Back()).(*pair[K, V]).key) } // 开始填充 c.data[key] = c.list.PushFront(&pair[K, V]{key: key, value: value}) }
2. [可选] 用例拓展
2.1 container/list.go
2.1.1 list 数据结构
上面 LRU Cache 代码中主要用了 "container/list" 双向链表保存新旧关系. 没有去造轮子自己来实现链式关系.
type Node[K comparable, V any] struct { key K value V prev *Node[K, V] next *Node[K, V] }
因为行家不喜欢总去当 🤡 . 简单去看看 contaner/list 数据结构布局.
// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package list implements a doubly linked list. // // To iterate over a list (where l is a *List): // for e := l.Front(); e != nil; e = e.Next() { // // do something with e.Value // } // package list // Element is an element of a linked list. type Element struct { // Next and previous pointers in the doubly-linked list of elements. // To simplify the implementation, internally a list l is implemented // as a ring, such that &l.root is both the next element of the last // list element (l.Back()) and the previous element of the first list // element (l.Front()). next, prev *Element // The list to which this element belongs. list *List // The value stored with this element. Value interface{} } // Next returns the next list element or nil. func (e *Element) Next() *Element { if p := e.next; e.list != nil && p != &e.list.root { return p } return nil } // Prev returns the previous list element or nil. func (e *Element) Prev() *Element { if p := e.prev; e.list != nil && p != &e.list.root { return p } return nil } // List represents a doubly linked list. // The zero value for List is an empty list ready to use. type List struct { root Element // sentinel list element, only &root, root.prev, and root.next are used len int // current list length excluding (this) sentinel element }
它是个特殊循环双向链表数据结构, 特殊之处在于 Element::List 指向头结点(root list). 属于 Go 自己的小聪明吧, 没事总要找点事情干干呗.
2.1.2 list 使用例子
func Test_List_Demo(t *testing.T) { // Persion 普通人 type Persion struct { Name string Age int } pers := list.New() // 链表数据填充 pers.PushBack(&Persion{Name: "wang", Age: 35}) pers.PushFront(&Persion{Name: "zhi", Age: 35}) fmt.Printf("List Len() = %d\n", pers.Len()) if pers.Len() != 2 { t.Fatal("pers.Len() != 2 data faild") } // 开始遍历数据 for element := pers.Front(); element != nil; element = element.Next() { per, ok := element.Value.(*Persion) if !ok { panic(fmt.Sprint("Persion list faild", element.Value)) } fmt.Println(per) } // 数据删除 for element := pers.Front(); element != nil; { next := element.Next() pers.Remove(element) element = next } fmt.Printf("List Len() = %d\n", pers.Len()) if pers.Len() != 0 { t.Fatal("pers.Len() != 0 data faild") } }
单元测试结果:
Running tool: /usr/local/go/bin/go test -timeout 30s -run ^Test_List_Demo$ demo/src/container/list -v -count=1 === RUN Test_List_Demo List Len() = 2 &{zhi 35} &{wang 35} List Len() = 0 --- PASS: Test_List_Demo (0.00s) PASS ok demo/src/container/list 0.002s
2.2 transport.go connLRU
list 在 Go 源码中一处小应用
// // src/net/http/transport.go // // persistConn wraps a connection, usually a persistent one // (but may be used for non-keep-alive requests as well) type persistConn struct { ...
..
. } type connLRU struct { ll *list.List // list.Element.Value type is of *persistConn m map[*persistConn]*list.Element } // add adds pc to the head of the linked list. func (cl *connLRU) add(pc *persistConn) { if cl.ll == nil { cl.ll = list.New() cl.m = make(map[*persistConn]*list.Element) } ele := cl.ll.PushFront(pc) if _, ok := cl.m[pc]; ok { panic("persistConn was already in LRU") } cl.m[pc] = ele } func (cl *connLRU) removeOldest() *persistConn { ele := cl.ll.Back() pc := ele.Value.(*persistConn) cl.ll.Remove(ele) delete(cl.m, pc) return pc } // remove removes pc from cl. func (cl *connLRU) remove(pc *persistConn) { if ele, ok := cl.m[pc]; ok { cl.ll.Remove(ele) delete(cl.m, pc) } } // len returns the number of items in the cache. func (cl *connLRU) len() int { return len(cl.m) }
3. 结尾
很多代码, 很多事情在平淡无奇处用心琢磨 ~ 可能长久 ~ 互相勉励 good luckly ~