-
Go LRU Cache
目录
1. LRU Cache
2. container/list.go
2.1 list 数据结构
2.2 list 使用例子
3. transport.go connLRU
4. 结尾
正文
1. LRU Cache
package cache import ( "container/list" "sync" ) // entry 存储的实体 type entry struct { key, val interface{} } // Cache 缓存结构 type Cache struct { // m 保证 LRU Cache 访问线程安全 rw sync.RWMutex // max 标识缓存容量的最大值, = 0 标识无限缓存 max int // list 是 entry 循环双向链表 list *list.List // pond entry 缓存池子 key -> entry pond map[interface{}]*list.Element } // New 构建 LRU Cache 缓存结构 func New(max int) *Cache { return &Cache{ max: max, list: list.New(), pond: make(map[interface{}]*list.Element), } } func (c *Cache) delete(key interface{}) { element, ok := c.pond[key] if ok { delete(c.pond, key) c.list.Remove(element) return } } // Set 设置缓存 func (c *Cache) Set(key, val interface{}) { c.rw.Lock() defer c.rw.Unlock() // 看是否进入删除分支 if val == nil { c.delete(key) return } element, ok := c.pond[key] if ok { // 重新设置 value 数据 element.Value.(*entry).val = val // set key nil exists 进入 update 逻辑 c.list.MoveToFront(element) return } // 首次添加 c.pond[key] = c.list.PushFront(&entry{key, val}) // 数据过多, 删除尾巴数据 if c.list.Len() > c.max && c.max > 0 { delete(c.pond, c.list.Remove(c.list.Back()).(*entry).key) } } // Get 获取缓存 func (c *Cache) Get(key interface{}) (val interface{}, ok bool) { c.rw.RLock() defer c.rw.RUnlock() if element, ok := c.pond[key]; ok { // 获取指定缓存值 val, ok = element.Value.(*entry).val, true // 调整缓存热点 c.list.MoveToFront(element) } return }
原理是 1. RWLock 做线程安全 2. list 双向链表保存时间新老关系 2. map 为了让时间复杂度到 O(1).
使用教程:
// 创建 c := cache.New(1) // 设置 c.Set("123", "123") c.Set("234", "234") // 使用 fmt.Println(c.Get("123")) fmt.Println(c.Get("234")) // 删除 c.Set("123", nil)
2. container/list.go
2.1 list 数据结构
上面 LRU Cache 代码中引用了 "container/list" , 简单分析下 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).
关于业务 list.go 具体实现部分我们不表.
2.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: 31}) pers.PushFront(&Persion{Name: "zhi", Age: 31}) 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 31} &{wang 31} List Len() = 0 --- PASS: Test_List_Demo (0.00s) PASS ok demo/src/container/list 0.002s
3. transport.go connLRU
抛一段 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) }
4. 结尾
很多代码, 很多事情也都平淡无奇, 但凡事种种都离不开用心, 反复琢磨 ~ 方能长久
欢迎批评指正交流 ~ good luckly ~
出处:https://www.cnblogs.com/life2refuel/p/15057872.html
栏目列表
最新更新
nodejs爬虫
Python正则表达式完全指南
爬取豆瓣Top250图书数据
shp 地图文件批量添加字段
爬虫小试牛刀(爬取学校通知公告)
【python基础】函数-初识函数
【python基础】函数-返回值
HTTP请求:requests模块基础使用必知必会
Python初学者友好丨详解参数传递类型
如何有效管理爬虫流量?
SQL SERVER中递归
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
这是目前我见过最好的跨域解决方案!
减少回流与重绘
减少回流与重绘
如何使用KrpanoToolJS在浏览器切图
performance.now() 与 Date.now() 对比
一款纯 JS 实现的轻量化图片编辑器
关于开发 VS Code 插件遇到的 workbench.scm.
前端设计模式——观察者模式
前端设计模式——中介者模式
创建型-原型模式