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)是一种类型。

Go语言基础之接口


分享: