LRU淘汰算法

LRU淘汰算法

LRU,全称为Least Recently Used,意思为最近最少使用,是一种内存淘汰算法,常用在缓存中作为一种淘汰策略。

计算机内存是有有限的,因此不可能无限的增加数据,缓存也是内存的一种,存在于内存中,缓存容量是有限制的,当缓存满了,就需要删除一些数据,给新的数据腾空间。

但这里有个问题是,应该删除哪些缓存呢?对于我们来说,肯定希望将没用的缓存删掉,留下有用的,但是怎么判定缓存是没用的?

常用的缓存淘汰算法有:FIFO(先进先出),LFU(Least Frequently Used 最少频率使用),LRU

接下来就来学习下LRU算法的实现,他要淘汰的数据是:最近最少使用的。

LRU算法解析

可以先用大白话来描述下LRU的实现步骤:

可以暂时先想象将所有缓存放在一个队列上,队列是按时间排列的,队列头为最近一次使用过的。

  1. 设置缓存,判断缓存是否已经存在了,存在则需要完成:
    1. 将缓存放在最前面(表示最近使用了)
    2. 更新缓存值
  2. 如果缓存不存在,则要新增缓存到最前面,并且判断缓存容量是否已满
    1. 如果缓存容量满了,则要将最后面的一个缓存删除
  3. 获取缓存,缓存存在,则获取,并且移动缓存到最前面

根据上面大致分析,其实LRU的核心就是一个数据结构,还需要一个容量,表示缓存容量,另外还需要提供两个接口:get(key)put(key, value),分别为获取缓存和设置缓存。

那我们接下来就要考虑使用哪些数据结构来实现了,从LRU的
根据算法的时间复杂度,get和put方法最理想的时间复杂度是O(1),而且LRU的操作里有查找,删除,插入,并且是有顺序之分

那有什么数据结构能满足以上条件的?

  1. 哈希表:哈希表查询快,但是数据是无序的
  2. 链表:链表是可以有序的,插入删除都快,但是查找很慢(要遍历整条链)
  3. 双向链表:满足插入删除快,并且操作头和尾很方便

所以要同时满足上述条件,我们可以考虑使用哈希表+双向链表来实现LRU,这两个数据结构的结合满足了条件。

LRU算法实现(Golang)

双向链表直接使用了Golang标准库的container/list,哈希表使用了map

package lru

import (
	"container/list"
	"fmt"
)

type Cache struct {
	capacity int                      // 缓存容量
	list     *list.List               // 双向链表
	hashmap  map[string]*list.Element // 哈希表
}

// 链表节点保存的数据,需要带上key
type CacheValue struct {
	key   string
	value interface{}
}

func NewCache(capactiy int) *Cache {
	return &Cache{
		capacity: capactiy,
		list:     new(list.List),
		hashmap:  make(map[string]*list.Element),
	}
}

// 获取缓存
func (c *Cache) Get(key string) (interface{}, bool) {
	// 判断缓存是否已存在
	if v, ok := c.hashmap[key]; ok { // 存在
		// 将key移到链表头
		c.list.MoveToFront(v)
		// 返回
		cacheValue := v.Value.(*CacheValue)
		return cacheValue.value, true
	}

	return nil, false
}

// 设置缓存
func (c *Cache) Put(key string, value interface{}) {
	// 缓存存在
	if v, ok := c.hashmap[key]; ok {
		// 将缓存移动到链表头
		c.list.MoveToFront(v)
		// 更新数据
		cacheValue := v.Value.(*CacheValue)
		cacheValue.value = value
	} else {
		// 缓存不存在,直接在链表头插入一个缓存
		e := c.list.PushFront(&CacheValue{key, value})
		// 插入哈希表
		c.hashmap[key] = e

		// 判断是否超过最大容量
		if len(c.hashmap) > c.capacity && c.capacity != 0 {
			// 获取链表尾巴的缓存
			back := c.list.Back()
			if back != nil {
				key := back.Value.(*CacheValue).key
				// 移除哈希表中的key
				delete(c.hashmap, key)
				// 移除链表尾巴的元素
				c.list.Remove(back)
			}
		}
	}
}

// 测试使用
func (c *Cache) Capacity() int {
	return c.capacity
}

func (c *Cache) PrintList() {
	ele := c.list.Front()
	for i := 0; i < c.list.Len(); i++ {
		kv := ele.Value.(*CacheValue)

		fmt.Printf("%v = %v ", kv.key, kv.value)

		ele = ele.Next()
	}
	fmt.Println()
}

测试:

package main

import (
	"alg/lru"
)

func main() {
	cache := lru.NewCache(5)

	cache.Put("key1", 1)
	cache.Put("key2", 2)
	cache.Put("key3", 3)
	cache.Put("key4", 4)
	cache.Put("key5", 5)

	// 测试1:获取key,验证是否会提取到链表头
	// value, ok := cache.Get("key2")
	// if !ok {
	// 	fmt.Println("get key2 fail")
	// } else {
	// 	fmt.Println("key2 = ", value)
	// }

	// cache.PrintList()

	// 测试2:缓存满了之后,新增是否会移除最少使用那个
	// cache.PrintList()

	// cache.Put("key6", 6)

	// cache.PrintList()

	// 测试3:缓存满了之后,更新已存在的值,看看是否会更新值并且移动到队列头
	cache.PrintList()

	cache.Put("key3", "key3")

	cache.PrintList()
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值