Skip to content

Commit 101db5c

Browse files
committed
docs(baguwen): 新增 LRU 和 LFU 每日一问
1 parent 194e9c7 commit 101db5c

1 file changed

Lines changed: 213 additions & 0 deletions

File tree

content/docs/baguwen/每日一问.md

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,219 @@ weight: 8
77

88
记录微信交流群中每天的提问。
99

10+
## 2026.06.09
11+
12+
### LRU 和 LFU 是什么?适用于什么场景,底层的实现原理是什么?
13+
14+
评级:LRU 手撕高频,LFU 可能会被提及
15+
16+
1. LRU 是 Least Recently Used,按最近访问时间淘汰数据。核心思想是时间局部性:最近访问过的数据,短期内再次访问概率更高。缓存容量满时,优先淘汰最久没有被访问的数据。
17+
2. LFU 是 Least Frequently Used,按访问频次淘汰数据。核心思想是稳定热点:访问次数越高的数据,未来继续被访问的概率更高。缓存容量满时,优先淘汰访问次数最低的数据。
18+
3. LRU 适合热点变化快、最近访问更能代表未来访问的场景,比如页面缓存、本地查询结果缓存、操作系统页缓存、InnoDB Buffer Pool 类缓存。
19+
4. LFU 适合长期热点明显、访问频次稳定、希望降低偶发访问干扰的场景,比如 Redis 热点 Key、CDN 静态资源缓存、商品详情缓存、推荐特征缓存。
20+
5. LRU 的底层实现通常是哈希表 + 双向链表。哈希表负责通过 key 在 O(1) 时间定位节点;双向链表负责维护访问顺序,链表头部放最近访问的数据,链表尾部放最久访问的数据。`Get` 命中后把节点移动到头部,`Put` 命中后更新值并移动到头部,容量满时删除尾部节点。
21+
6. LFU 的底层实现通常是 `key -> node` 哈希表 + `freq -> 双向链表` 哈希表 + `minFreq`。每个节点记录 key、value、freq;同一访问频次的节点放在同一条双向链表里,链表内部再按 LRU 顺序排列。`Get` 命中后频次加一,把节点从旧频次链表移动到新频次链表;`Put` 容量满时,从 `minFreq` 对应链表的尾部淘汰一个节点。
22+
7. 工程取舍:LRU 实现简单、维护成本低,对最近访问敏感;LFU 更能保留长期高频热点,但频次统计有额外维护成本,热点迁移时会受历史频次影响,生产实现常加入频次衰减或近似统计。
23+
8. 面试收口:LRU 看最近一次访问时间,核心结构是 `map + 双向链表`;LFU 看访问次数,核心结构是 `map + freqMap + minFreq + 双向链表`。手写重点是保证 `Get``Put`、移动节点和淘汰节点都是 O(1)。
24+
25+
#### LRU 示例代码
26+
27+
```go
28+
package main
29+
30+
import "fmt"
31+
32+
type lruNode struct {
33+
key, value int
34+
prev, next *lruNode
35+
}
36+
37+
type LRUCache struct {
38+
capacity int
39+
items map[int]*lruNode
40+
head *lruNode
41+
tail *lruNode
42+
}
43+
44+
func NewLRUCache(capacity int) *LRUCache {
45+
head, tail := &lruNode{}, &lruNode{}
46+
head.next = tail
47+
tail.prev = head
48+
49+
return &LRUCache{
50+
capacity: capacity,
51+
items: make(map[int]*lruNode),
52+
head: head,
53+
tail: tail,
54+
}
55+
}
56+
57+
func (c *LRUCache) Get(key int) int {
58+
node, ok := c.items[key]
59+
if !ok {
60+
return -1
61+
}
62+
c.moveToHead(node)
63+
return node.value
64+
}
65+
66+
func (c *LRUCache) Put(key, value int) {
67+
if c.capacity <= 0 {
68+
return
69+
}
70+
71+
if node, ok := c.items[key]; ok {
72+
node.value = value
73+
c.moveToHead(node)
74+
return
75+
}
76+
77+
node := &lruNode{key: key, value: value}
78+
c.items[key] = node
79+
c.addToHead(node)
80+
81+
if len(c.items) > c.capacity {
82+
removed := c.removeTail()
83+
delete(c.items, removed.key)
84+
}
85+
}
86+
87+
func (c *LRUCache) moveToHead(node *lruNode) {
88+
c.remove(node)
89+
c.addToHead(node)
90+
}
91+
92+
func (c *LRUCache) addToHead(node *lruNode) {
93+
node.prev = c.head
94+
node.next = c.head.next
95+
c.head.next.prev = node
96+
c.head.next = node
97+
}
98+
99+
func (c *LRUCache) remove(node *lruNode) {
100+
node.prev.next = node.next
101+
node.next.prev = node.prev
102+
}
103+
104+
func (c *LRUCache) removeTail() *lruNode {
105+
node := c.tail.prev
106+
c.remove(node)
107+
return node
108+
}
109+
110+
func main() {
111+
cache := NewLRUCache(2)
112+
cache.Put(1, 1)
113+
cache.Put(2, 2)
114+
fmt.Println(cache.Get(1)) // 1
115+
cache.Put(3, 3)
116+
fmt.Println(cache.Get(2)) // -1,key 2 被淘汰
117+
}
118+
```
119+
120+
#### LFU 示例代码
121+
122+
```go
123+
package main
124+
125+
import (
126+
"container/list"
127+
"fmt"
128+
)
129+
130+
type lfuEntry struct {
131+
key, value int
132+
freq int
133+
}
134+
135+
type LFUCache struct {
136+
capacity int
137+
size int
138+
minFreq int
139+
items map[int]*list.Element
140+
freqs map[int]*list.List
141+
}
142+
143+
func NewLFUCache(capacity int) *LFUCache {
144+
return &LFUCache{
145+
capacity: capacity,
146+
items: make(map[int]*list.Element),
147+
freqs: make(map[int]*list.List),
148+
}
149+
}
150+
151+
func (c *LFUCache) Get(key int) int {
152+
element, ok := c.items[key]
153+
if !ok {
154+
return -1
155+
}
156+
157+
entry := element.Value.(*lfuEntry)
158+
c.increaseFreq(element)
159+
return entry.value
160+
}
161+
162+
func (c *LFUCache) Put(key, value int) {
163+
if c.capacity <= 0 {
164+
return
165+
}
166+
167+
if element, ok := c.items[key]; ok {
168+
entry := element.Value.(*lfuEntry)
169+
entry.value = value
170+
c.increaseFreq(element)
171+
return
172+
}
173+
174+
if c.size == c.capacity {
175+
listByFreq := c.freqs[c.minFreq]
176+
back := listByFreq.Back()
177+
entry := back.Value.(*lfuEntry)
178+
delete(c.items, entry.key)
179+
listByFreq.Remove(back)
180+
c.size--
181+
}
182+
183+
entry := &lfuEntry{key: key, value: value, freq: 1}
184+
listByFreq := c.getList(1)
185+
c.items[key] = listByFreq.PushFront(entry)
186+
c.minFreq = 1
187+
c.size++
188+
}
189+
190+
func (c *LFUCache) increaseFreq(element *list.Element) {
191+
entry := element.Value.(*lfuEntry)
192+
oldFreq := entry.freq
193+
oldList := c.freqs[oldFreq]
194+
oldList.Remove(element)
195+
196+
if oldFreq == c.minFreq && oldList.Len() == 0 {
197+
c.minFreq++
198+
}
199+
200+
entry.freq++
201+
newList := c.getList(entry.freq)
202+
c.items[entry.key] = newList.PushFront(entry)
203+
}
204+
205+
func (c *LFUCache) getList(freq int) *list.List {
206+
if c.freqs[freq] == nil {
207+
c.freqs[freq] = list.New()
208+
}
209+
return c.freqs[freq]
210+
}
211+
212+
func main() {
213+
cache := NewLFUCache(2)
214+
cache.Put(1, 1)
215+
cache.Put(2, 2)
216+
fmt.Println(cache.Get(1)) // 1,key 1 的频次变成 2
217+
cache.Put(3, 3)
218+
fmt.Println(cache.Get(2)) // -1,key 2 频次最低,被淘汰
219+
fmt.Println(cache.Get(3)) // 3
220+
}
221+
```
222+
10223
## 2026.06.08
11224

12225
### 短视频平台点赞系统高并发设计

0 commit comments

Comments
 (0)