深度解析:146. LRU 缓存(哈希表 + 双向链表的艺术)

LRU (Least Recently Used) 的核心在于:"如果一个数据最近被访问过,那么它在未来被访问的概率也更高。"

为了达到 的操作效率,我们需要两种数据结构的结合:

  1. 哈希表 (Map) :提供 的随机访问能力,通过 key 快速定位节点。
  2. 双向链表 (Doubly Linked List):提供 的节点移动能力,用来维护访问顺序。

实现细节优化

1. 定义双向链表节点

使用双向链表是因为删除一个节点时,我们需要知道它的前驱节点。

javascript 复制代码
class ListNode {
    constructor(key, value) {
        this.key = key;
        this.value = value;
        this.prev = null;
        this.next = null;
    }
}
2. LRU 缓存类定义

技巧: 使用"哨兵节点"(Dummy Head/Tail)可以极大简化边界条件的处理,避免判断 null

javascript 复制代码
class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.map = new Map();
        
        // 初始化哨兵节点,它们不存储实际数据
        this.head = new ListNode(-1, -1);
        this.tail = new ListNode(-1, -1);
        this.head.next = this.tail;
        this.tail.prev = this.head;
    }

关键内部方法(封装逻辑)

将复杂的链表操作封装为私有方法,使 getput 的主逻辑像英语一样易读。

javascript 复制代码
    // 将节点移至头部(表示最近使用)
    moveToHead(node) {
        this.removeNode(node);
        this.addToHead(node);
    }

    // 从链表中彻底断开节点
    removeNode(node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    // 在哨兵头节点后插入新节点
    addToHead(node) {
        node.prev = this.head;
        node.next = this.head.next;
        this.head.next.prev = node;
        this.head.next = node;
    }

    // 弹出最久未使用的节点(尾部哨兵前的一个)
    removeTail() {
        const lastNode = this.tail.prev;
        this.removeNode(lastNode);
        return lastNode; // 返回节点以便从 map 中删除
    }

主接口实现

javascript 复制代码
    /** * @param {number} key
     * @return {number}
     */
    get(key) {
        if (!this.map.has(key)) return -1;
        
        const node = this.map.get(key);
        this.moveToHead(node); // 刷新位置
        return node.value;
    }

    /** * @param {number} key 
     * @param {number} value
     */
    put(key, value) {
        if (this.map.has(key)) {
            const node = this.map.get(key);
            node.value = value; // 更新值
            this.moveToHead(node);
        } else {
            const newNode = new ListNode(key, value);
            this.map.set(key, newNode);
            this.addToHead(newNode);
            
            if (this.map.size > this.capacity) {
                const removed = this.removeTail();
                this.map.delete(removed.key); // 必须在节点中存储 key 才能在此删除 map 记录
            }
        }
    }
}
相关推荐
AI进化营-智能译站3 小时前
ROS2 C++开发系列08-传感器数据缓存与指令解析方式之数组、向量与字符串实战
开发语言·c++·缓存·ai
许彰午3 小时前
CacheSQL(一):手写数据库的工程化重生
java·数据库·缓存
jinyishu_3 小时前
链表经典OJ题
c语言·数据结构·算法·链表
aXin_ya4 小时前
微服务第九天 分布式缓存(Redis)
分布式·缓存·微服务
代码飞天4 小时前
CTF之内存取证——瞬息万变成为一瞬
安全·web安全·缓存
jieyucx4 小时前
Go 零基础数据结构:链表的增删改查(像串珠子一样简单)
数据结构·链表·golang
许彰午4 小时前
CacheSQL(四):CacheSQLClient——用一张路由表实现水平扩展
java·数据库·缓存·系统架构·政务
Lyyaoo.4 小时前
缓存穿透/雪崩/击穿
数据库·缓存·oracle
许彰午5 小时前
CacheSQL(三):双 HTTP 引擎与 SQL 查询——接口抽象的价值
java·数据库·sql·缓存
Flying pigs~~14 小时前
RAG智慧问答项目
数据库·人工智能·缓存·微调·知识库·rag