深度解析: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 记录
            }
        }
    }
}
相关推荐
cfm_29148 小时前
Redis缓存规范设计与全方位性能优化实战
redis·缓存·性能优化
不会就选b12 小时前
数据结构之顺序表和链表的OJ题(上)
数据结构·链表
苏渡苇13 小时前
Redis 持久化——RDB 快照 vs AOF 日志
数据库·redis·缓存·redis持久化·aof vs rdb
小碗羊肉15 小时前
【Redis | 第六篇】Redisson
数据库·redis·缓存
如竟没有火炬16 小时前
寻找峰值——二分
java·开发语言·数据结构·python·算法·散列表
AwakeFantasy16 小时前
关于Codex中转站生图比例问题的解决记录
数据库·redis·缓存
剑傲娇17 小时前
【计算机组成原理】 数据通路 之单总线结构
缓存
夜白宋18 小时前
【Redis深入】一、快的原因
数据库·redis·缓存
宇砾19 小时前
浅谈Redis(2)
数据库·redis·缓存
cfm_291419 小时前
Redis Stack 零基础入门
数据库·redis·缓存