📌 实现思路
一、核心目标
我们要实现一个缓存类:
- 支持通过
get(key)
获取缓存的值; - 支持通过
put(key, value)
写入缓存; - 缓存容量有限,当超过容量时要淘汰最久未使用的元素。
二、为什么用「哈希表 + 双向链表」
功能 | 使用的结构 | 原因 |
---|---|---|
快速查找 key | 哈希表 (dict ) |
O(1) 时间复杂度 |
快速移动元素到头部 | 双向链表 | O(1) 移除 / 插入节点,无需整体移动元素 |
快速删除最旧元素 | 链表尾部淘汰 | 尾节点指针指向最久未使用项,删除也只需 O(1) 操作 |
🧾 完整代码 + 注释
// 声明一个通用的 LRU 缓存类,Key 必须是 Hashable 的,Value 任意类型
class LRUCache<Key: Hashable, Value> {
// 内部节点类:用于双向链表的节点结构
private class Node {
let key: Key
var value: Value
var prev: Node?
var next: Node?
init(key: Key, value: Value) {
self.key = key
self.value = value
}
}
private let capacity: Int // 最大缓存容量
private var dict: [Key: Node] = [:] // 哈希表,用于快速访问节点
private var head: Node? // 链表头(最近使用)
private var tail: Node? // 链表尾(最久未使用)
// 初始化函数
init(capacity: Int) {
self.capacity = capacity
}
// 读取缓存
func get(_ key: Key) -> Value? {
guard let node = dict[key] else {
return nil // 如果找不到,返回 nil
}
moveToHead(node) // 将访问的节点移到头部,表示“最近被使用”
return node.value
}
// 写入缓存
func put(_ key: Key, value: Value) {
if let node = dict[key] {
// 已存在,更新值并移到头部
node.value = value
moveToHead(node)
} else {
// 新节点,插入到头部
let newNode = Node(key: key, value: value)
dict[key] = newNode
addToHead(newNode)
// 如果超过容量,移除尾部最久未使用的节点
if dict.count > capacity {
if let removed = removeTail() {
dict.removeValue(forKey: removed.key)
}
}
}
}
// 添加节点到链表头部
private func addToHead(_ node: Node) {
node.next = head
node.prev = nil
head?.prev = node
head = node
if tail == nil {
tail = head
}
}
// 从链表中移除某个节点
private func removeNode(_ node: Node) {
if let prev = node.prev {
prev.next = node.next
} else {
head = node.next // node 是头部
}
if let next = node.next {
next.prev = node.prev
} else {
tail = node.prev // node 是尾部
}
}
// 将某个节点移到头部(表示最近使用)
private func moveToHead(_ node: Node) {
removeNode(node)
addToHead(node)
}
// 移除尾部节点(最久未使用的)
private func removeTail() -> Node? {
guard let oldTail = tail else { return nil }
removeNode(oldTail)
return oldTail
}
}
📈 使用示例(调试输出)
let cache = LRUCache<String, Int>(capacity: 2)
cache.put("a", value: 1)
cache.put("b", value: 2)
print(cache.get("a") ?? -1) // 输出 1 ("a" 成为最近使用)
cache.put("c", value: 3) // 淘汰 "b",因为 "b" 是最久未使用的
print(cache.get("b") ?? -1) // 输出 -1(已被淘汰)
🔍 运行原理图解
每次执行操作时,双向链表的结构如下所示(假设 head 在左,tail 在右):
- 初始放入
a
→head = a
,tail = a
- 放入
b
→a <-> b
- 访问
a
:a
移到头部 →a <-> b
- 放入
c
,超过容量,淘汰尾部b
→a <-> c
✅ 总结亮点
特性 | 实现方式 |
---|---|
泛型支持 | <Key: Hashable, Value> 泛型设计 |
O(1) 查找 | 使用 Dictionary |
O(1) 插入删除 | 使用双向链表 |
高复用性 | 泛型设计支持任意 Key/Value 类型 |