LRU算法

LRU

LRU算法可以用于内存淘汰策略,策略是最近最少使用,使用双线链表可以轻松满足这个要求,新元素插入表头,元素被get将其移动到表头,淘汰表尾元素,缺陷在于会有大量的内存碎片和指针内存消耗、内存拷贝,查找、删除时间复杂度都为O(n),因为要遍历查找相应元素。双向链表+hash的方式可以解决这些问题,使得插入、查找、删除都是O(1)

LRU实现

// 双向链表结点
type dllNode struct {
	value      interface{}
	prev, next *dllNode
}

// LRU
// hash指向node+链表使得插入、删除、查找都是O(1)
type LRUCache struct {
	head, tail *dllNode            // 头尾结点
	elen       int                 // 当前元素个数
	ecap       int                 // 缓存大小
	hash       map[string]*dllNode // key:node
}

func NewLRUCache(maxCacheLen int) *LRUCache {
	head, tail := new(dllNode), new(dllNode)
	head.next = tail
	tail.prev = head
	return &LRUCache{
		head: head,
		tail: tail,
		elen: 0,
		ecap: maxCacheLen,
		hash: make(map[string]*dllNode, maxCacheLen),
	}
}

func newdllNode(value interface{}) *dllNode {
	return &dllNode{value: value}
}

// 插入
func (lru *LRUCache) Save(key string, value interface{}) {
	node, ok := lru.hash[key]
	// 已经存在了
	if ok {
		lru.moveToHead(node)
		return
	}

	// 已经满了则删除一个
	if lru.elen == lru.ecap {
		lru.removetail()
	}

	newNode := newdllNode(value)
	lru.hash[key] = newNode
	lru.elen++

	// 放在表头
	newNode.next = lru.head.next
	lru.head.next.prev = newNode

	newNode.prev = lru.head
	lru.head.next = newNode
}

// 当缓存满了,移除tail
func (lru *LRUCache) removetail() {
	if lru.elen == 0 {
		return
	}

	lru.tail.prev = lru.tail.prev.prev
	lru.tail.prev.next = lru.tail
}

// 删除
func (lru *LRUCache) Delete(key string) {

}

// 获取,应该移动到表头
func (lru *LRUCache) Get(key string) interface{} {
	movenode, ok := lru.hash[key]
	if !ok {
		return nil
	}

	lru.moveToHead(movenode)

	return movenode.value
}

// 移动到表头
func (lru *LRUCache) moveToHead(node *dllNode) {
	node.prev.next = node.next
	node.next.prev = node.prev

	node.next = lru.head.next
	lru.head.next.prev = node

	lru.head.next = node
	node.prev = lru.head
}

func (lru *LRUCache) print() {
	node := lru.head.next
	for node != lru.tail {
		fmt.Print(node.value, " ")
		node = node.next
	}
}

func main() {
	lru := NewLRUCache(4)
	lru.Save("key1", 1)
	lru.Save("key2", 2)
	lru.Save("key3", 3)
	lru.Save("key4", 4)
	lru.print() // 4 3 2 1

	fmt.Println()

	lru.Save("key5", 5)
	lru.print() // 5 4 3 2

	fmt.Println()

	lru.Get("key2")
	lru.print() // 2 5 4 3
}

Redis中的LRU

双向链表+hash的方式仍然会造成大量的内存消耗,redis追求极致速度,采用的是近似LRU的方案,随机选取几个键,删除最久未使用的元素。偶然使用一次的元素可能也不会被删除,考虑到这Redis5.0推出LFU的内存淘汰策略,使用最近访问频率来标定热度,考虑是否淘汰

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值