LRU(Least Recently Used,最近最少使用)缓存是一种常见的缓存策略,经典的实现方式是结合哈希表和双向链表:哈希表用于快速查找缓存项,双向链表用于维护缓存项的访问顺序。那我们要如何用代码实现呢,之前我在深入解析Go语言container/list:双向链表的实现与应用中介绍了go语言中的双向链表,这可以减少我们对链表的实现,这里只需实现一个LRUCache结构体,用于存放哈希表和双向链表。
LRU 的读写时序图
在开始代码编写前,先简单说下以 LRU 策略的缓存读写的流程:
读场景:
- 首先用户先从缓存中查找
- 缓存命中的话,需要将命中的数据放到链表的头部再返回值
- 如果不存在就返回一个不存在的标识(例如 -1)
写场景:
- 用户往缓存中插入一个键值对,首先查看缓存中是否存在
- 如果存在,更新值,且将元素移到链表头部
- 如果不存在,则先将数据插入到链表头部,如何将键值对插入到缓存中
- 插入缓存的过程需要判断当前缓存的大小是否超过容量
- 如果超过,则移除链表尾部的元素,并将其在缓存中删除
具体的流程如下时序图所示:

有了上面简单的方案,我们接下来只需按照时序图实现相关的操作即可。
1. 定义缓存结构
创建一个结构体来表示 LRU 缓存,包含缓存容量、用于存储键值对的哈希表以及用于记录使用顺序的双向链表。
go
import (
"container/list"
)
type LRUCache struct {
capacity int
cache map[int]*list.Element
lruList *list.List
}
capacity
表示缓存的最大容量,cache
是一个哈希表,用于快速查找缓存项,lruList
是一个双向链表,用于记录缓存项的使用顺序。
2. 实现初始化方法
实现一个初始化方法,用于创建一个新的 LRU 缓存实例。
go
func Constructor(capacity int) LRUCache {
return LRUCache{
capacity: capacity,
cache: make(map[int]*list.Element),
lruList: list.New(),
}
}
3. 实现 Get 方法
Get
方法用于从缓存中获取指定键的值。如果键存在于缓存中,就将对应的缓存项移到链表头部(表示最近被使用),并返回其值;如果键不存在,就返回 -1。
go
func (l *LRUCache) Get(key int) int {
if elem, ok := l.cache[key]; ok {
// 将访问的元素移到链表头部
l.lruList.MoveToFront(elem)
return elem.Value.(*list.Element).Value.(int)
}
return -1
}
4. 实现 Put 方法
Put
方法用于将键值对添加到缓存中。如果键已经存在,就更新其值,并将对应的缓存项移到链表头部;如果键不存在,就添加一个新的缓存项到链表头部,并将键值对添加到哈希表中。如果缓存已满,则需要移除链表尾部的缓存项(最久未使用的)。
go
func (l *LRUCache) Put(key int, value int) {
if elem, ok := l.cache[key]; ok {
// 更新值
lruElem := elem.Value.(*list.Element)
lruElem.Value = value
// 将访问的元素移到链表头部
l.lruList.MoveToFront(elem)
} else {
// 添加新的元素到链表头部
elem := l.lruList.PushFront(&list.Element{Value: value})
l.cache[key] = elem
// 如果缓存已满,移除链表尾部的元素
if len(l.cache) > l.capacity {
backElem := l.lruList.Back()
delete(l.cache, backElem.Value.(*list.Element).Value.(int))
l.lruList.Remove(backElem)
}
}
}
5. 使用示例
go
func main() {
cache := Constructor(2) // 创建一个容量为 2 的缓存
cache.Put(1, 1)
cache.Put(2, 2)
fmt.Println(cache.Get(1)) // 返回 1
cache.Put(3, 3) // 缓存已满,移除键 2
fmt.Println(cache.Get(2)) // 返回 -1 (不命中)
cache.Put(4, 4) // 缓存已满,移除键 1
fmt.Println(cache.Get(1)) // 返回 -1 (不命中)
fmt.Println(cache.Get(3)) // 返回 3
fmt.Println(cache.Get(4)) // 返回 4
}
以上就是用 Go 语言实现 LRU 缓存的一个简单示例。