@@ -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