LRU:least recently used. 是一种逐出策略,既可用在缓存中,也可用在操作系统的页面置换中,还有其他的类似场景。
LRU的算法,此处就不赘述了,很好理解。因为要把最近访问的元素放在队尾(或队首),所以用数组的操作复杂度是O(n),用链表的复杂度是O(1)。
最简单的cache一般要考虑这几个点:
- capacity,容量。
- data,存储缓存数据。一般是map结构,通过key快速定位到数据。
- 逐出策略。缓存是有容量的,所以需要一个逐出算法,替换data中的数据。
- key生成函数。
- 数据回源函数。
- Get函数。
- 回源策略。(本题目不涉及)
Leetcode.146
核心是链表的操作;这里有一个技巧,就是把 header 和 tail 设置成常驻的空节点,这样可以保证每个数据节点的 pre 和 next 不为空,少了很多 nil 判断。
type LRUCache struct {
capacity int
data map[int]*Node
header *Node // 空节点
tail *Node // 空节点
}
type Node struct {
key int // 用于在 delete 的时候直接拿到 key
val int
pre *Node
next *Node
}
func Constructor(capacity int) LRUCache {
lc := LRUCache{
capacity: capacity,
data: make(map[int]*Node, capacity),
header: &Node{},
tail: &Node{},
}
lc.header.next = lc.tail
lc.tail.pre = lc.header
return lc
}
func (this *LRUCache) Get(key int) int {
res := -1 // default value
if v, ok := this.data[key]; ok {
res = v.val
// 把 v 断开
v.pre.next = v.next
v.next.pre = v.pre
// 把 v 加到 tail 前面
v.pre = this.tail.pre
v.next = this.tail
v.pre.next = v
v.next.pre = v
}
return res
}
func (this *LRUCache) Put(key int, value int) {
if v, ok := this.data[key]; ok { // 已存在,只更新
v.val = value
this.Get(key) // 用get刷新
return
}
for len(this.data) >= this.capacity { // 容量已满,先清理
rmk := this.header.next.key
delete(this.data, rmk)
this.header.next = this.header.next.next
this.header.next.pre = this.header
}
nn := &Node{key: key, val: value}
this.data[key] = nn
nn.pre = this.tail.pre
nn.pre.next = nn
nn.next = this.tail
this.tail.pre = nn
}