LRU缓存

LRU缓存

其实LRU不难实现,但是要实现高效的LRU比较难,就是要实现增删查改均为O(1)

理解核心思想

想象LRU缓存就像一个只能放3本书的小书架:

  • 最近看过的书要放在最左边
  • 当书架满了要加新书时,把最右边(最久没看)的书扔掉

数据结构的作用(非常简单!)

  • list<pair<int, int>> cache :就是那个小书架 ,按阅读顺序放书 list.front()= 最近看的(最左边) list.back()= 最久没看的(最右边)
  • unordered_map<int, ...> map :是书的索引卡片 通过书名(key)快速找到书在书架上的位置 值类型是list<pair<int, int>>::iterator= 书在书架上的位置指针

关键操作拆解(对应现实动作)

1. get操作:找书看

复制代码
int get(int key) {
    // 1. 先查索引卡:这本书在书架上吗?
    auto it = map.find(key);  // it是指向索引卡条目的指针
    if (it == map.end()) return -1; // 没这本书
    
    // 2. 找到书了!把这本书抽出来放到最左边
    // cache.splice(位置, 书架, 书的位置) = 把书从当前位置移到最前面
    cache.splice(cache.begin(), cache, it->second);
    
    // 3. 返回书的内容
    return it->second->second; // it->second是书的位置,->second是书的内容
}

2. put操作:放书

复制代码
void put(int key, int value) {
    // 情况1:书已存在(更新)
    auto it = map.find(key);
    if (it != map.end()) {
        it->second->second = value; // 更新书的内容
        cache.splice(cache.begin(), cache, it->second); // 移到最前面
        return;
    }
    
    // 情况2:新书,但书架满了
    if (cache.size() == cap) {
        // 扔掉最右边的书
        int key_to_del = cache.back().first; // 最旧书的书名
        map.erase(key_to_del);  // 从索引卡中撕掉这张卡片
        cache.pop_back();       // 把书从书架最右边扔掉
    }
    
    // 把新书放到最左边
    cache.push_front({key, value}); // 书架上放新书
    map[key] = cache.begin();      // 索引卡记录:书名->书的位置(最左边)
}

迭代器到底是什么?(超简单理解)

把list::iterator就想象成书签 或手指:

  • 它指向链表中的某个具体位置
  • it->second就是通过书签拿到指向的那本书

完整模拟过程(cap=2)

复制代码
操作序列:put(1,1), put(2,2), get(1), put(3,3)

1. put(1,1): 书架=[(1,1)], 索引卡:1->指向(1,1)
2. put(2,2): 书架=[(2,2),(1,1)], 索引卡:1->指向(1,1), 2->指向(2,2)  
3. get(1): 找到书1,移到最前:书架=[(1,1),(2,2)]
4. put(3,3): 书架已满,扔掉最右边的(2,2)
   - 先更新索引卡:删除key=2
   - 书架变成[(1,1)],然后插入(3,3)到最前:[(3,3),(1,1)]

class LRUCache {
private:
    int cap;
    list<pair<int, int>> cache; // 书架:存放(key,value)对
    unordered_map<int, list<pair<int, int>>::iterator> map; // 索引卡:key->书的位置

public:
    LRUCache(int capacity) : cap(capacity) {}

    int get(int key) {
        // 在索引卡中找这本书
        auto map_it = map.find(key);
        if (map_it == map.end()) return -1; // 没找到
        
        // map_it->second 是书在书架上的位置(迭代器)
        // 把这本书移到书架最前面
        cache.splice(cache.begin(), cache, map_it->second);
        
        // 返回书的内容:map_it->second指向书,->second是value
        return map_it->second->second;
    }

    void put(int key, int value) {
        auto map_it = map.find(key);
        
        if (map_it != map.end()) {
            // 书已存在:更新内容并移到最前
            map_it->second->second = value; // 更新书的内容
            cache.splice(cache.begin(), cache, map_it->second);
            return;
        }
        
        // 新书,但书架满了
        if (cache.size() == cap) {
            // 扔掉最旧的书(书架最后面)
            pair<int, int> old_book = cache.back(); // 最旧的书
            map.erase(old_book.first);  // 从索引卡删除
            cache.pop_back();           // 从书架扔掉
        }
        
        // 插入新书到书架最前面
        cache.push_front({key, value});
        // 更新索引卡:记录新书的位置(书架最前面)
        map[key] = cache.begin();
    }
};

关键记住:

  • list是物理书架,存实际数据
  • unordered_map是索引卡,只存位置指针
  • 所有操作都要同时更新书架和索引卡
相关推荐
2501_933923251 小时前
redis:string类型常见命令
数据库·redis·缓存
黄连木笛3 小时前
Redis Insight 汉化教程:Windows / macOS / Linux 设置中文界面(附繁體、教程汉化、关闭自动更新)
数据库·redis·缓存
威嵌神州17 小时前
嵌入式排故实录:高并发下串口数据“超时丢包”的根因剖析与架构优化
运维·服务器·嵌入式硬件·缓存
nice_lcj5201 天前
LRU 缓存入门:它是什么、怎么用、面试怎么手写
java·数据结构·缓存·面试·lru
羑悻的小杀马特2 天前
Redis持久化三剑客:RDB二进制快照、AOF文本日志、混合模式如何守护你的数据安全?
数据库·redis·缓存·aof·持久化存储·rdb
时间的拾荒人2 天前
Redis 进阶实战:渐进式遍历、持久化机制与事务详解
数据库·redis·缓存
2501_933923253 天前
解读Redis:单线程模型
数据库·redis·缓存
时间的拾荒人3 天前
Redis 集合类型详解:Set 与 Zset
redis·缓存·面试
Alluxio4 天前
Alluxio + OCI:为 AI 工作负载实现亚毫秒访问延迟与可扩展吞吐
运维·数据库·人工智能·分布式·缓存·ai·语言模型