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是索引卡,只存位置指针
  • 所有操作都要同时更新书架和索引卡
相关推荐
知我Deja_Vu3 天前
redisCommonHelper.generateCode(“GROUP“),Redis 生成码方法
数据库·redis·缓存
没有bug.的程序员3 天前
电商秒杀系统深度进阶:高并发流量建模、库存零超卖内核与 Redis+MQ 闭环
数据库·redis·缓存·高并发·电商秒杀·流量建模·库存零超卖
troublea3 天前
ThinkPHP3.x高效学习指南
mysql·nginx·缓存
troublea3 天前
ThinkPHP6快速入门指南
数据库·mysql·缓存
Emotional。3 天前
AI Agent 性能优化和成本控制
人工智能·深度学习·机器学习·缓存·性能优化
jnrjian3 天前
Oracle 共享池 库缓存下的 Library Cache Lock
数据库·缓存·oracle
Anastasiozzzz4 天前
阿亮随手记:MySQL移除查询缓存、子查询优化深分页、自增主键溢出、索引失效
数据库·mysql·缓存
難釋懷4 天前
Redis消息队列-基于Stream的消息队列-消费者组
数据库·redis·缓存
難釋懷4 天前
Redis消息队列-基于Stream的消息队列
数据库·redis·缓存
troublea4 天前
Laravel 8.x新特性全解析
数据库·mysql·缓存