一、LRU(Least Recently Used)简介
LRU 是一种缓存淘汰算法,其核心思想是当缓存容量达到上限时,移除那些最近最少使用的数据项。换句话说,LRU 算法认为最近没有被访问的数据在未来被访问的可能性也比较低,因此可以优先被淘汰。
二、实现一个 LRU 缓存
实现一个高效的 LRU 缓存通常需要考虑快速查找、插入和删除操作。常用的方法是结合哈希表(用于O(1)时间复杂度的查找)和双向链表(用于维护元素的使用顺序,并支持O(1)时间复杂度的插入与删除)。下面是一个简单的实现思路:
1. 数据结构设计
- 使用双向链表来存储缓存中的数据项,以便我们可以快速地在任意位置插入和删除节点。
- 使用哈希表将键映射到链表中的节点,从而支持 O(1) 的查找时间。
2. 主要操作
- get(key): 如果键存在于缓存中,则返回对应的值,并将该节点移到链表头部(表示最新访问)。如果不存在,则返回 -1。
- put(key, value): 如果键已经存在,则覆盖旧值,并将该节点移到链表头部。如果键不存在,则添加新节点到链表头部。如果添加后超过最大容量,则移除链表尾部的节点(最久未使用)。
3. 示例代码(Python)
class DLinkedNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.cache = dict() # 哈希表,键 -> 节点引用
# 使用伪头部和伪尾部节点
self.head = DLinkedNode()
self.tail = DLinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
self.capacity = capacity
self.size = 0
def get(self, key: int) -> int:
if key not in self.cache:
return -1
# 如果 key 存在,先通过哈希表定位,再移到头部
node = self.cache[key]
self.moveToHead(node)
return node.value
def put(self, key: int, value: int) -> None:
if key not in self.cache:
# 如果 key 不存在,创建一个新的节点
node = DLinkedNode(key, value)
# 添加进哈希表
self.cache[key] = node
# 添加至双向链表的头部
self.addToHead(node)
self.size += 1
if self.size > self.capacity:
# 如果超出容量,删除双向链表的尾部节点
removed = self.removeTail()
# 删除哈希表中对应的项
self.cache.pop(removed.key)
self.size -= 1
else:
# 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node = self.cache[key]
node.value = value
self.moveToHead(node)
def addToHead(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def removeNode(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def moveToHead(self, node):
self.removeNode(node)
self.addToHead(node)
def removeTail(self):
node = self.tail.prev
self.removeNode(node)
return node
4. 关键方法解释
- addToHead(node):将指定节点加到双向链表的头部。
- removeNode(node):从双向链表中删除指定节点。
- moveToHead(node):先删除节点再将其添加到链表头部,表示最近访问。
- removeTail():移除链表尾部节点,并返回该节点,用于淘汰策略。
这种实现方式保证了所有关键操作(如 get
和 put
)的时间复杂度均为 O(1),非常适合于实现高效且动态变化的 LRU 缓存系统。
下面我将分别用 PHP 和 Go 实现一个 LRU缓存(Least Recently Used),基于你上面提到的使用 哈希表 + 双向链表 的方式。
5. 其他语言实现
✅ (一)PHP 实现 LRU 缓存
<?php
class DLinkedNode {
public $key;
public $value;
public $prev;
public $next;
public function __construct($key = null, $value = null) {
$this->key = $key;
$this->value = $value;
$this->prev = null;
$this->next = null;
}
}
class LRUCache {
private $capacity;
private $cache = [];
private $head;
private $tail;
private $size = 0;
public function __construct($capacity) {
$this->capacity = $capacity;
$this->head = new DLinkedNode();
$this->tail = new DLinkedNode();
$this->head->next = $this->tail;
$this->tail->prev = $this->head;
}
public function get($key) {
if (!isset($this->cache[$key])) {
return -1;
}
$node = $this->cache[$key];
$this->moveToHead($node);
return $node->value;
}
public function put($key, $value) {
if (!isset($this->cache[$key])) {
$node = new DLinkedNode($key, $value);
$this->cache[$key] = $node;
$this->addToHead($node);
$this->size++;
if ($this->size > $this->capacity) {
$removed = $this->removeTail();
unset($this->cache[$removed->key]);
$this->size--;
}
} else {
$node = $this->cache[$key];
$node->value = $value;
$this->moveToHead($node);
}
}
private function addToHead($node) {
$node->prev = $this->head;
$node->next = $this->head->next;
$this->head->next->prev = $node;
$this->head->next = $node;
}
private function removeNode($node) {
$node->prev->next = $node->next;
$node->next->prev = $node->prev;
}
private function moveToHead($node) {
$this->removeNode($node);
$this->addToHead($node);
}
private function removeTail() {
$node = $this->tail->prev;
$this->removeNode($node);
return $node;
}
}
// 示例
$cache = new LRUCache(2);
$cache->put(1, 1);
$cache->put(2, 2);
echo $cache->get(1) . "\n"; // 返回 1
$cache->put(3, 3); // 淘汰 key=2
echo $cache->get(2) . "\n"; // 返回 -1
$cache->put(4, 4); // 淘汰 key=1
echo $cache->get(1) . "\n"; // 返回 -1
echo $cache->get(3) . "\n"; // 返回 3
echo $cache->get(4) . "\n"; // 返回 4
✅ (二)Go 实现 LRU 缓存
package main
import "fmt"
type DLinkedNode struct {
key int
value int
prev *DLinkedNode
next *DLinkedNode
}
type LRUCache struct {
capacity int
cache map[int]*DLinkedNode
head *DLinkedNode
tail *DLinkedNode
size int
}
func Constructor(capacity int) LRUCache {
lru := LRUCache{
capacity: capacity,
cache: make(map[int]*DLinkedNode),
head: &DLinkedNode{},
tail: &DLinkedNode{},
size: 0,
}
lru.head.next = lru.tail
lru.tail.prev = lru.head
return lru
}
func (c *LRUCache) Get(key int) int {
if node, ok := c.cache[key]; !ok {
return -1
} else {
c.moveToHead(node)
return node.value
}
}
func (c *LRUCache) Put(key, value int) {
if node, ok := c.cache[key]; !ok {
node = &DLinkedNode{key: key, value: value}
c.cache[key] = node
c.addToHead(node)
c.size++
if c.size > c.capacity {
removed := c.removeTail()
delete(c.cache, removed.key)
c.size--
}
} else {
node.value = value
c.moveToHead(node)
}
}
func (c *LRUCache) addToHead(node *DLinkedNode) {
node.prev = c.head
node.next = c.head.next
c.head.next.prev = node
c.head.next = node
}
func (c *LRUCache) removeNode(node *DLinkedNode) {
node.prev.next = node.next
node.next.prev = node.prev
}
func (c *LRUCache) moveToHead(node *DLinkedNode) {
c.removeNode(node)
c.addToHead(node)
}
func (c *LRUCache) removeTail() *DLinkedNode {
node := c.tail.prev
c.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
}