Golang实现LRU
目录:
LRU
LRU算法即淘汰最近一段时间内没有被访问(命中)的数据,最理想的实现方法为双链表+Hash表。
- 双链表:新加入/新命中的节点放到表尾,空间用完需要淘汰的时候,删除表头(最近一段时间没有被命中)。
- 双链表可以使添加删除的时间复杂度降到
O(1)
- 双链表可以使添加删除的时间复杂度降到
- Hash表:访问的时间复杂度降到
O(1)
实现
具体流程写在注释内:
使用List包
package Cache
import (
"container/list"
"errors"
)
type node struct {
key string
value interface{}
}
type LRUCache struct {
Cap int // 容量
dList *list.List // 双链表
hashMap map[string]*list.Element
}
func New(cap int) *LRUCache {
return &LRUCache{
Cap: cap,
dList: list.New(),
hashMap: make(map[string]*list.Element, cap),
}
}
func (lc *LRUCache) Volume() int {
return lc.dList.Len()
}
func (lc *LRUCache) Get(k string) (interface{}, error) {
if lc.dList == nil {
return nil, errors.New("LRU not initialized")
}
// 命中就更新到list尾
if v, ok := lc.hashMap[k]; ok {
lc.dList.MoveToBack(v)
return v.Value.(*node).value, nil
}
return nil, nil
}
func (lc *LRUCache) Set(k string, v interface{}) error {
if lc.dList == nil {
return errors.New("LRU not initialized")
}
// 命中,直接更新
if v, ok := lc.hashMap[k]; ok {
lc.dList.MoveToBack(v)
lc.hashMap[k].Value.(*node).value = v
return nil
}
// 未命中
// 超出容量需要淘汰表头
if lc.dList.Len() >= lc.Cap {
ft := lc.dList.Front()
if ft == nil {
return nil
}
// 移除头部 并 从map删除(remove回返回ft的value对象,所以直接取值)
delete(lc.hashMap, lc.dList.Remove(ft).(*node).key)
}
// List尾部插入 并 插入map
lc.hashMap[k] = lc.dList.PushBack(&node{key: k, value: v})
return nil
}
func (lc *LRUCache) Del(k string) error {
if lc.dList == nil {
return errors.New("LRU not initialized")
}
if v, ok := lc.hashMap[k]; ok {
// 从list移除 并 从map删除
delete(lc.hashMap, lc.dList.Remove(v).(*node).key)
return nil
}
return nil
}
手动构造双链表
package LRU
// 双向链表
type Node struct {
Key string // 键和值
Value interface{}
pre *Node // 头节点
next *Node // 尾节点
}
// LRU缓存
type LRUCache struct {
volume int // 空间大小
HashMap map[string]*Node // 哈希表
head *Node // 头节点
end *Node // 尾节点
}
func New(volume int) *LRUCache {
return &LRUCache{
volume: volume,
HashMap: make(map[string]*Node, volume),
head: nil,
end: nil,
}
}
// 取数据
func (l *LRUCache) Get(key string) interface{} {
// 命中:放到表尾,返回值
if v, ok := l.HashMap[key]; ok {
l.refreshNode(v)
return v.Value
}
return -1
}
func (l *LRUCache) Set(key string, value interface{}) {
// 设置命中:放到表尾
if v, ok := l.HashMap[key]; ok {
v.Value = value
l.refreshNode(v)
return
}
// 未命中,添加节点
if len(l.HashMap) >= l.volume {
// 1. 淘汰表头
k := l.removeNode(l.head)
// 2. 删除Hash内节点
delete(l.HashMap, k)
}
node := &Node{
Key: key,
Value: value,
}
l.addNode(node)
l.HashMap[key] = node
}
// 移除双链表节点,返回键值
func (l *LRUCache) removeNode(node *Node) string {
// 尾节点
if node == l.end {
l.end = l.end.pre
l.end.next = nil
return node.Key
}
// 头节点
if node == l.head {
l.head = l.head.next
l.head.pre = nil
return node.Key
}
// 中间
node.pre.next = node.next
node.next.pre = node.pre
return node.Key
}
func (l *LRUCache) addNode(node *Node) {
if l.end != nil {
l.end.next = node
node.next = nil
}
l.end = node
if l.head == nil {
l.head = node
}
}
func (l *LRUCache) refreshNode(node *Node) {
if node == l.end {
return
}
l.removeNode(node)
l.addNode(node)
}
快速提取接口
在接收者结构体内任意位置->右键->Refactor
->Extrace Interface...
在Go语言中接口(interface)是一种类型,一种抽象的类型。
interface是一组method的集合,是duck-type programming的一种体现。接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。
为了保护你的Go语言职业生涯,请牢记接口(interface)是一种类型。