使用 Swift 实现 LRU 缓存淘汰策略

📌 实现思路

一、核心目标

我们要实现一个缓存类:

  • 支持通过 get(key) 获取缓存的值;
  • 支持通过 put(key, value) 写入缓存;
  • 缓存容量有限,当超过容量时要淘汰最久未使用的元素

二、为什么用「哈希表 + 双向链表」

功能 使用的结构 原因
快速查找 key 哈希表 (dict) O(1) 时间复杂度
快速移动元素到头部 双向链表 O(1) 移除 / 插入节点,无需整体移动元素
快速删除最旧元素 链表尾部淘汰 尾节点指针指向最久未使用项,删除也只需 O(1) 操作

🧾 完整代码 + 注释

swift 复制代码
// 声明一个通用的 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
    }
}

📈 使用示例(调试输出)

swift 复制代码
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 在右):

  • 初始放入 ahead = a, tail = a
  • 放入 ba <-> b
  • 访问 aa 移到头部 → a <-> b
  • 放入 c,超过容量,淘汰尾部 ba <-> c

✅ 总结亮点

特性 实现方式
泛型支持 <Key: Hashable, Value> 泛型设计
O(1) 查找 使用 Dictionary
O(1) 插入删除 使用双向链表
高复用性 泛型设计支持任意 Key/Value 类型

相关推荐
予早32 分钟前
Redis 设置库的数量
数据库·redis·缓存
黑金IT1 小时前
vLLM本地缓存实战,重复提交直接复用不浪费算力
人工智能·缓存
Rick19933 小时前
Redis查询为什么快
数据库·redis·缓存
Rick19934 小时前
Redis 底层架构图
数据库·redis·缓存
Arva .5 小时前
Redis 数据类型
数据库·redis·缓存
笑我归无处5 小时前
Redis和数据库的数据一致性问题研究
数据库·redis·缓存
小红的布丁6 小时前
操作系统与高性能 IO:零拷贝、一次读 IO、CPU 缓存与伪共享
缓存
SPC的存折6 小时前
(自用)LNMP-Redis-Discuz5.0部署指南-openEuler24.03-测试环境
linux·运维·服务器·数据库·redis·缓存
用户79457223954137 小时前
【AFNetworking】OC 时代网络请求事实标准,Alamofire 的前身
objective-c·swift
Flying pigs~~8 小时前
RAG前身:基于mysql➕redis➕bm25的传统QA问答系统
数据库·redis·缓存·大模型·qa·rag·prompt提示词