文章目录
- type Node struct { key, value int prev, next *Node }
- type LRUCache struct { capacity int cache map[int]*Node head *Node tail *Node } head 和 tail 是哨兵节点(dummy),方便操作。
- func Constructor(capacity int) LRUCache { head := &Node{} tail := &Node{} head.next = tail tail.prev = head return LRUCache{ capacity: capacity, cache: make(map[int]*Node), head: head, tail: tail, } }
- // moveToHead:将节点移动到头部 func (l *LRUCache) moveToHead(node *Node) { l.removeNode(node) l.addToHead(node) } // removeNode:删除链表中的节点 func (l *LRUCache) removeNode(node *Node) { prev := node.prev next := node.next prev.next = next next.prev = prev } // addToHead:在头部插入节点 func (l *LRUCache) addToHead(node *Node) { node.prev = l.head node.next = l.head.next l.head.next.prev = node l.head.next = node } // removeTail:删除尾部节点并返回它 func (l *LRUCache) removeTail() *Node { node := l.tail.prev l.removeNode(node) return node }
-
- func (l *LRUCache) Get(key int) int { if node, ok := l.cache[key]; ok { l.moveToHead(node) return node.value } return -1 }
- func (l *LRUCache) Put(key int, value int) { if node, ok := l.cache[key]; ok { node.value = value l.moveToHead(node) } else { newNode := &Node{key: key, value: value} l.cache[key] = newNode l.addToHead(newNode) if len(l.cache) > l.capacity { tail := l.removeTail() delete(l.cache, tail.key) } } }
目录
- 一、LRU缓存机制简介
- 1. 定义
- 2. 使用场景
- 二、设计要求
- 三、核心数据结构
- 1. 哈希表(map)
- 2. 双向链表
- 四、Go语言实现
- 1. 节点结构
- 2. LRU缓存结构
- 3. 初始化
- 4. 辅助方法
- 5. 核心操作
- Get
- Put
- 五、完整代码示例
- 六、复杂度分析
- 七、工程实践与优化
- 八、应用场景
- 九、总结
在高性能服务开发中,缓存是提升访问速度和减少后端负载的重要手段。常见的缓存淘汰策略中,**LRU(Least Recently Used,最近最少使用)**是应用最广的一种。本篇我们用Go语言手写一个LRU缓存机制的模拟实现。
LRU缓存是一种固定容量的缓存结构。当缓存已满时,它会淘汰最近最少使用的那个数据。简单理解:
谁最久没被访问,就先删除谁。
- Web浏览器缓存
- 数据库查询结果缓存
- 操作系统页面置换
LRU缓存应支持以下操作:
Get(key):如果key存在,返回对应的value,并将该key标记为最近使用;否则返回-1。Put(key, value):插入或更新key。如果容量已满,需要删除最近最少使用的key。
要求两种操作都能在 O(1) 时间复杂度内完成。
要实现 O(1) 操作,需要组合以下两种结构:
- 用于存储
key → 节点 的映射;
- 可在 O(1) 时间内找到节点。
key → 节点 的映射;
- 用于维护数据访问的顺序;
- 头部表示最近使用,尾部表示最久未使用;
- 插入、删除节点都是 O(1)。
type Node struct {
key, value int
prev, next *Node
}
type LRUCache struct {
capacity int
cache map[int]*Node
head *Node
tail *Node
}
head 和 tail 是哨兵节点(dummy),方便操作。
head 和 tail 是哨兵节点(dummy),方便操作。
func Constructor(capacity int) LRUCache {
head := &Node{}
tail := &Node{}
head.next = tail
tail.prev = head
return LRUCache{
capacity: capacity,
cache: make(map[int]*Node),
head: head,
tail: tail,
}
}
// moveToHead:将节点移动到头部
func (l *LRUCache) moveToHead(node *Node) {
l.removeNode(node)
l.addToHead(node)
}
// removeNode:删除链表中的节点
func (l *LRUCache) removeNode(node *Node) {
prev := node.prev
next := node.next
prev.next = next
next.prev = prev
}
// addToHead:在头部插入节点
func (l *LRUCache) addToHead(node *Node) {
node.prev = l.head
node.next = l.head.next
l.head.next.prev = node
l.head.next = node
}
// removeTail:删除尾部节点并返回它
func (l *LRUCache) removeTail() *Node {
node := l.tail.prev
l.removeNode(node)
return node
}
func (l *LRUCache) Get(key int) int {
if node, ok := l.cache[key]; ok {
l.moveToHead(node)
return node.value
}
return -1
}
func (l *LRUCache) Put(key int, value int) {
if node, ok := l.cache[key]; ok {
node.value = value
l.moveToHead(node)
} else {
newNode := &Node{key: key, value: value}
l.cache[key] = newNode
l.addToHead(newNode)
if len(l.cache) > l.capacity {
tail := l.removeTail()
delete(l.cache, tail.key)
}
}
}
package main
import "fmt"
type Node struct {
key, value int
prev, next *Node
}
type LRUCache struct {
capacity int
cache map[int]*Node
head *Node
tail *Node
}
func Constructor(capacity int) LRUCache {
head := &Node{}
tail := &Node{}
head.next = tail
tail.prev = head
return LRUCache{
capacity: capacity,
cache: make(map[int]*Node),
head: head,
tail: tail,
}
}
func (l *LRUCache) Get(key int) int {
if node, ok := l.cache[key]; ok {
l.moveToHead(node)
return node.value
}
return -1
}
func (l *LRUCache) Put(key int, value int) {
if node, ok := l.cache[key]; ok {
node.value = value
l.moveToHead(node)
} else {
newNode := &Node{key: key, value: value}
l.cache[key] = newNode
l.addToHead(newNode)
if len(l.cache) > l.capacity {
tail := l.removeTail()
delete(l.cache, tail.key)
}
}
}
func (l *LRUCache) moveToHead(node *Node) {
l.removeNode(node)
l.addToHead(node)
}
func (l *LRUCache) removeNode(node *Node) {
prev := node.prev
next := node.next
prev.next = next
next.prev = prev
}
func (l *LRUCache) addToHead(node *Node) {
node.prev = l.head
node.next = l.head.next
l.head.next.prev = node
l.head.next = node
}
func (l *LRUCache) removeTail() *Node {
node := l.tail.prev
l.removeNode(node)
return node
}
func main() {
cache := Constructor(2)
cache.Put(1, 1)
cache.Put(2, 2)
fmt.Println(cache.Get(1)) // 1
cache.Put(3, 3) // 淘汰 key=2
fmt.Println(cache.Get(2)) // -1
cache.Put(4, 4) // 淘汰 key=1
fmt.Println(cache.Get(1)) // -1
fmt.Println(cache.Get(3)) // 3
fmt.Println(cache.Get(4)) // 4
}
- 时间复杂度:O(1),Get 和 Put 都只涉及哈希表查找和链表操作。
- 空间复杂度:O(capacity),存储固定大小的map和链表节点。
- 1. 线程安全
在多协程环境中,需使用 sync.Mutex 或 sync.RWMutex 保证安全。
- 2. 泛型支持(Go1.18+)
可以用泛型实现支持任意类型的key/value。
- 3. 监控统计
可增加命中率统计、淘汰计数。
在多协程环境中,需使用
sync.Mutex 或 sync.RWMutex 保证安全。可以用泛型实现支持任意类型的key/value。
可增加命中率统计、淘汰计数。
- 数据库缓存:Redis内部就支持LRU策略;
- 浏览器缓存:网页资源加载优化;
- API限速器:存储用户最近访问记录。
- LRU缓存结合了 哈希表 + 双向链表;
- 关键是 O(1) 时间内完成访问和淘汰;
- 该思想可扩展到 LFU、ARC 等高级缓存策略。
到此这篇关于Go语言中LRU缓存机制的实现的文章就介绍到这了,更多相关Go语言 LRU缓存内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!
您可能感兴趣的文章:
- 如何通过Golang的container/list实现LRU缓存算法
- 使用Go语言实现LRU缓存的代码详解
- go语言实现LRU缓存的示例代码
- 基于Golang container/list实现LRU缓存
- golang实现LRU缓存淘汰算法的示例代码