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是索引卡,只存位置指针
  • 所有操作都要同时更新书架和索引卡
相关推荐
写后端的胖头鱼7 小时前
一文详解Cache Aside(旁路缓存模式)
redis·缓存·延迟双删·缓存一致
code_slave(码畜)8 小时前
微服务架构落地:基础服务 —— 报表服务(中篇:元数据、查询引擎、缓存与权限落地)
spring boot·spring cloud·缓存·微服务·架构
hweiyu0012 小时前
Redis命令:HPEXPIREAT
redis·缓存
code2cat13 小时前
【随笔】MCP缓存期限与共享范围:让Agent复用资料时记住边界
java·后端·缓存·ai agent·mcp
吴声子夜歌15 小时前
Nginx应用与运维——Nginx缓存服务应用实战(一)
运维·nginx·缓存
ShineWinsu15 小时前
对于Redis:ZSet类型的解析
数据库·c++·redis·缓存·面试·有序集合·zset
hweiyu0016 小时前
Redis命令:HPTTL
redis·缓存
ly768917 小时前
Spring Boot 集成 Redis 企业级实践:连接池、序列化与缓存穿透雪崩的工程化防御
spring boot·redis·缓存·缓存穿透·布隆过滤器·lettuce 连接池
ShineWinsu2 天前
对于Redis:Hash类型的解析
c++·redis·分布式·缓存·面试·hash·哈希表
.Hypocritical.2 天前
Redis从入门到实战:核心原理、场景落地与避坑指南
数据库·redis·缓存