在技术面试中,LRU(Least Recently Used)算法是一个高频考点,尤其对于后端开发岗位。面试官往往会从操作系统的基础概念切入,逐步深入到工业级系统的实现细节。本文将带你穿透LRU算法的理论本质与工程实践,掌握从页面置换到缓存淘汰的全链路知识。
LRU算法的核心逻辑是"最近最久未使用的数据优先被淘汰"。这种思想源于计算机科学的局部性原理——程序在执行过程中更倾向于访问最近使用过的数据。在操作系统中,LRU被用作页面置换算法;在数据库领域,MySQL的Buffer Pool管理采用类似机制;而在分布式缓存系统如Redis中,LRU及其变种是关键的缓存淘汰策略。
LRU的四个基本特征:
手动模拟LRU页面置换的过程:
| 访问序列 | 内存状态(容量3) | 缺页 | 淘汰页面 |
|---|---|---|---|
| 1 | [1] | 是 | - |
| 2 | [2, 1] | 是 | - |
| 3 | [3, 2, 1] | 是 | - |
| 1 | [1, 3, 2] | 否 | - |
| 4 | [4, 1, 3] | 是 | 2 |
注意:实际面试中可能需要手写这类模拟过程,建议用双向链表+哈希表的结构来解释时间复杂度
最经典的LRU实现采用双向链表+哈希表的结构:
python复制class LRUCache:
def __init__(self, capacity: int):
self.cache = {}
self.capacity = capacity
self.head = Node(0, 0)
self.tail = Node(0, 0)
self.head.next = self.tail
self.tail.prev = self.head
def get(self, key: int) -> int:
if key in self.cache:
node = self.cache[key]
self._remove(node)
self._add(node)
return node.value
return -1
def put(self, key: int, value: int) -> None:
if key in self.cache:
self._remove(self.cache[key])
node = Node(key, value)
self._add(node)
self.cache[key] = node
if len(self.cache) > self.capacity:
node = self.tail.prev
self._remove(node)
del self.cache[node.key]
这种实现的时间复杂度:
在生产环境中,严格的LRU实现可能因为性能开销而不被采用。Redis采用了一种近似LRU算法,通过随机采样来平衡精度与性能:
Redis 3.0后引入的LFU(Least Frequently Used)可以看作是LRU的进阶版,同时考虑访问频率和访问时间。
标准LRU在写密集场景下可能出现缓存污染问题。改进方案包括:
java复制// LRU-K的简化实现框架
public class LRUKCache<K, V> {
private final int k;
private final Map<K, List<Long>> accessHistory;
private final Map<K, V> cache;
public V get(K key) {
recordAccess(key);
return cache.get(key);
}
private void recordAccess(K key) {
List<Long> history = accessHistory.getOrDefault(key, new ArrayList<>());
history.add(System.nanoTime());
if (history.size() > k) {
history.remove(0);
}
accessHistory.put(key, history);
}
}
时钟算法是LRU的近似实现,被广泛应用于操作系统和数据库系统:
现代系统通常使用改进型时钟算法,额外考虑修改位(dirty bit)来减少磁盘I/O:
| 页面状态 | 处理策略 |
|---|---|
| (0,0) | 直接淘汰 |
| (0,1) | 写回磁盘后淘汰 |
| (1,0) | 置为(0,0)继续扫描 |
| (1,1) | 置为(0,1)继续扫描 |
当面试官要求设计类似Redis的缓存系统时,LRU的实现需要考虑:
bash复制# Redis相关配置示例
maxmemory 2gb
maxmemory-policy allkeys-lru
maxmemory-samples 5
LRU策略需要与以下机制配合使用:
实际工程中,LRU很少单独使用。比如在MySQL的InnoDB引擎中:
sql复制-- 查看InnoDB缓冲池状态
SHOW ENGINE INNODB STATUS\G
-- 关键指标:
-- Pages made young/not young
-- youngs/s non-youngs/s
在Linux内核中,页面缓存管理同样采用类似Clock算法的变种。可以通过vmstat观察页面置换情况:
bash复制vmstat -s | grep -E 'pages|swap'
理解这些系统级实现,能帮助我们在面试中展现出对LRU算法更深入的工程认知。