lru缓存核心是淘汰最久未被访问元素,需哈希表+双向链表实现o(1)查找、移动和删除;php中推荐spldoublylinkedlist配合数组映射,但offsetunset等操作为o(n),高频场景应自定义链表节点。

LRU 缓存的核心逻辑是什么?
LRU(Least Recently Used)要求在容量满时,淘汰**最久未被访问**的元素。关键不是“最早加入”,而是“最近一次访问时间最靠前”。所以必须同时支持:快速查找(O(1))、快速移动到最新位置(O(1))、快速删除最旧节点(O(1))。纯数组或普通哈希表无法同时满足——PHP 中最常用解法是 哈希表 + 双向链表组合。
PHP 中怎么高效实现双向链表结构?
不建议手写完整链表类。PHP 7.4+ 推荐用 SplDoublyLinkedList,它原生支持头尾增删、迭代、偏移定位,且内部是双向链表实现。但注意:SplDoublyLinkedList 的 offsetGet() 是 O(n),不能用来随机查节点;所以仍需搭配关联数组(array)做 key → node 映射。典型结构:
- 一个
array $map:存储 key ⇒ SplDoublyLinkedList 节点(实际存的是 value,但需能反查 key) - 一个
SplDoublyLinkedList $list:按访问顺序维护节点,头部为最新,尾部为最旧 - 每次
get():查 map → 找到节点 → 从 list 中 detach → push front; - 每次
put():若存在则更新 value 并 move to front;若不存在且满,则 pop back(最旧)并 unset map 对应 key,再 unshift 新节点
面试时容易被追问的关键细节
别只写骨架代码。面试官常盯住这几个点:
- 如何保证 get/put 都是 O(1)? —— map 提供 O(1) 查找;SplDoublyLinkedList 的 unshift/pop/rewind/next 是均摊 O(1),detach 需要先定位,但我们可以不 detach,改用“删除后重建”或更稳妥的“手动维护指针”(即自定义链表节点类)
-
PHP 数组键名是否区分大小写?key 是字符串还是整型? —— LRU 缓存 key 应支持任意可哈希类型,实际中通常限定为 string/int,构造时可加
is_scalar($key)校验 - 并发安全吗? —— 单线程没问题;若提 Web 场景,可说明“PHP-FPM 每请求单进程,天然隔离”,无需加锁;Swoole 或多线程环境才需考虑
- 内存泄漏风险? —— 注意不要在节点里循环引用自身或闭包,避免 GC 无法回收;SplDoublyLinkedList 节点本身不含引用陷阱,相对安全
一个简洁可用的 PHP LRU 实现(带注释)
以下代码满足面试基本要求,无依赖,可直接运行:
立即学习“PHP免费学习笔记(深入)”;
class LRUCache {
private array $map = [];
private SplDoublyLinkedList $list;
private int $capacity;
public function __construct(int $capacity) {
$this->capacity = $capacity;
$this->list = new SplDoublyLinkedList();
$this->list->setIteratorMode(SplDoublyLinkedList::IT_MODE_FIFO);
}
public function get(mixed $key): mixed {
if (!isset($this->map[$key])) return -1;
// 把对应节点移到头部(最新)
$node = $this->map[$key];
$this->list->offsetUnset($this->findIndex($key)); // O(n),仅用于演示;真实高频场景建议自定义链表
$this->list->unshift($node);
$this->map[$key] = $node;
return $node['value'];
}
public function put(mixed $key, mixed $value): void {
if (isset($this->map[$key])) {
// 更新值并移到头部
$this->list->offsetUnset($this->findIndex($key));
$this->list->unshift(['key' => $key, 'value' => $value]);
$this->map[$key] = ['key' => $key, 'value' => $value];
return;
}
// 新增
if ($this->list->count() >= $this->capacity && $this->capacity > 0) {
// 删除尾部(最久未用)
$tail = $this->list->pop();
unset($this->map[$tail['key']]);
}
$this->list->unshift(['key' => $key, 'value' => $value]);
$this->map[$key] = ['key' => $key, 'value' => $value];
}
private function findIndex(mixed $key): int {
$i = 0;
foreach ($this->list as $node) {
if ($node['key'] === $key) return $i;
$i++;
}
return -1;
}
}
注意:findIndex 是 O(n),仅用于教学清晰性。高分答案会主动指出这点,并给出优化方向:用自定义双向链表节点(含 prev/next),配合 map 存储节点引用,实现真正 O(1) 的 move-to-front。











