实现一个 LRU 风格的缓存类

news2025/2/6 8:01:19

实现一个缓存类

    • 需求描述
    • 豆包
      • 解决思路:
      • 实现代码:
      • 优化1
        • 1. `std::list::remove` 的时间复杂度问题
        • 2. 代码复用
        • 优化后的代码
        • 优化说明
      • 优化2
        • 1. 边界条件检查
        • 2. 异常处理
        • 3. 代码封装性
        • 4. 线程安全
        • 优化后的代码示例
        • 优化说明
    • DeepSeek(深度思考R1):
      • 代码实现
        • 实现说明:
      • 优化方案一:基础互斥锁版本
      • 优化方案二:读写锁版本(C++17+)
      • 优化分析说明:

需求描述

类似于Windows系统的 win 菜单最近打开功能模块,使用最近最少使用(LRU)的缓存淘汰策略,缓存可以设置最大容量。
get:获取缓存数据,不存在时返回 -1
put:新增或者更新缓存,当容量已满时,删除最早的数据,再插入

class UpdateCaching {
	public:
		UpdateCaching(int capacity){}
		int get(int key){}
		void put(int key ,int value){}
}

豆包

解决思路:

  1. 使用 std::unordered_map 存储键值对,以便快速查找和更新数据。(为啥使用 unordered_map 而不是 map)

  2. 使用 std::list 存储键的访问顺序,以便在容量满时删除最早的数据。

  3. put 操作中:

    • 检查缓存是否已满,如果已满,删除 list 中的第一个元素,并从 unordered_map 中移除对应的键值对。
    • 将新元素添加到 unordered_map 中,并将键添加到 list 的末尾。
  4. get 操作中:

    • 使用 unordered_map 查找键是否存在,如果存在,将键移到 list 的末尾,表示最近访问过。

实现代码:

#include <iostream>
#include <unordered_map>
#include <list>

class UpdateCaching {
private:
    std::unordered_map<int, int> cache;
    std::list<int> keyOrder;
    int capacity;

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

    int get(int key) {
        if (cache.count(key) == 0) {
            return -1;
        }
        // 将访问的键移到列表末尾,表示最近使用过
        keyOrder.remove(key);
        keyOrder.push_back(key);
        return cache[key];
    }

    void put(int key, int value) {
        if (cache.count(key) > 0) {
            // 键已存在,更新值并将其移到列表末尾
            cache[key] = value;
            keyOrder.remove(key);
            keyOrder.push_back(key);
            return;
        }
        if (cache.size() >= capacity) {
            // 缓存已满,移除最久未使用的元素
            int oldestKey = keyOrder.front();
            keyOrder.pop_front();
            cache.erase(oldestKey);
        }
        // 插入新元素
        cache[key] = value;
        keyOrder.push_back(key);
    }
};

int main() {
    UpdateCaching cache(3);
    cache.put(1, 10);
    cache.put(2, 20);
    cache.put(3, 30);
    std::cout << cache.get(2) << std::endl;  // 输出 20
    cache.put(4, 40);  // 缓存已满,删除最早的元素 (1, 10)
    std::cout << cache.get(1) << std::endl;  // 输出 -1
    return 0;
}

优化1

1. std::list::remove 的时间复杂度问题

getput 方法中,使用 keyOrder.remove(key) 来移动元素到列表末尾。std::list::remove 的时间复杂度是 O ( n ) O(n) O(n),因为它需要遍历整个列表来找到要删除的元素。可以通过维护一个指向每个键在列表中位置的映射,将这一操作的时间复杂度优化到 O ( 1 ) O(1) O(1)

2. 代码复用

getput 方法中都有将键移到列表末尾的操作,可以将这一操作提取成一个单独的方法,提高代码的复用性。

优化后的代码
#include <iostream>
#include <unordered_map>
#include <list>

class UpdateCaching {
private:
    std::unordered_map<int, int> cache;
    std::unordered_map<int, std::list<int>::iterator> keyIterators;
    std::list<int> keyOrder;
    int capacity;

    // 将键移到列表末尾
    void moveToBack(int key) {
        auto it = keyIterators[key];
        keyOrder.erase(it);
        keyOrder.push_back(key);
        keyIterators[key] = --keyOrder.end();//更新映射容器中key对应的迭代器
    }

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

    int get(int key) {
        if (cache.count(key) == 0) {
            return -1;
        }
        // 将访问的键移到列表末尾,表示最近使用过
        moveToBack(key);
        return cache[key];
    }

    void put(int key, int value) {
        if (cache.count(key) > 0) {
            // 键已存在,更新值并将其移到列表末尾
            cache[key] = value;
            moveToBack(key);
            return;
        }
        if (cache.size() >= capacity) {
            // 缓存已满,移除最久未使用的元素
            int oldestKey = keyOrder.front();
            keyOrder.pop_front();
            cache.erase(oldestKey);
            keyIterators.erase(oldestKey);
        }
        // 插入新元素
        cache[key] = value;
        keyOrder.push_back(key);
        keyIterators[key] = --keyOrder.end();
    }
};

int main() {
    UpdateCaching cache(3);
    cache.put(1, 10);
    cache.put(2, 20);
    cache.put(3, 30);
    std::cout << cache.get(2) << std::endl;  // 输出 20
    cache.put(4, 40);  // 缓存已满,删除最早的元素 (1, 10)
    std::cout << cache.get(1) << std::endl;  // 输出 -1
    return 0;
}
优化说明
  1. 引入 keyIterators 映射:用于存储每个键在 keyOrder 列表中的迭代器,这样在移动元素到列表末尾时,就可以直接通过迭代器删除元素,时间复杂度为 O ( 1 ) O(1) O(1)
  2. 提取 moveToBack 方法:将将键移到列表末尾的操作提取成一个单独的方法,提高了代码的复用性和可读性。

通过这些优化,getput 方法的时间复杂度都可以优化到 O ( 1 ) O(1) O(1)

优化2

1. 边界条件检查

在构造函数中可以添加对 capacity 参数的检查,避免传入不合法的值(如负数),增强代码的健壮性。

2. 异常处理

在实际应用中,std::unordered_mapstd::list 的操作可能会抛出异常(如内存分配失败),可以考虑添加异常处理机制,让代码在异常情况下能有更好的表现。

3. 代码封装性

可以提供一些额外的接口方法,如获取缓存当前大小、检查缓存是否为空等,方便调用者使用。

4. 线程安全

如果该缓存类要在多线程环境下使用,需要添加同步机制,保证线程安全。

优化后的代码示例
#include <iostream>
#include <unordered_map>
#include <list>
#include <mutex>

class UpdateCaching {
private:
    std::unordered_map<int, int> cache;
    std::unordered_map<int, std::list<int>::iterator> keyIterators;
    std::list<int> keyOrder;
    int capacity;
    mutable std::mutex mtx;  // 用于线程安全的互斥锁

    // 将键移到列表末尾
    void moveToBack(int key) {
        std::lock_guard<std::mutex> lock(mtx);  // 加锁,在get或put中被调用时已经加锁了,且是同一个锁 mtx,不会再次进行真正的加锁操作
        auto it = keyIterators[key];
        keyOrder.erase(it);
        keyOrder.push_back(key);
        keyIterators[key] = --keyOrder.end();
    }

public:
    // 构造函数添加边界检查
    UpdateCaching(int cap) : capacity(cap) {
        if (cap <= 0) {
            throw std::invalid_argument("Capacity must be a positive integer.");
        }
    }

    int get(int key) {
        std::lock_guard<std::mutex> lock(mtx);  // 加锁
        if (cache.count(key) == 0) {
            return -1;
        }
        // 将访问的键移到列表末尾,表示最近使用过
        moveToBack(key);
        return cache[key];
    }

    void put(int key, int value) {
        std::lock_guard<std::mutex> lock(mtx);  // 加锁
        if (cache.count(key) > 0) {
            // 键已存在,更新值并将其移到列表末尾
            cache[key] = value;
            moveToBack(key);
            return;
        }
        if (cache.size() >= capacity) {
            // 缓存已满,移除最久未使用的元素
            int oldestKey = keyOrder.front();
            keyOrder.pop_front();
            cache.erase(oldestKey);
            keyIterators.erase(oldestKey);
        }
        // 插入新元素
        cache[key] = value;
        keyOrder.push_back(key);
        keyIterators[key] = --keyOrder.end();
    }

    // 获取缓存当前大小
    size_t size() const {
        std::lock_guard<std::mutex> lock(mtx);  // 加锁
        return cache.size();
    }

    // 检查缓存是否为空
    bool empty() const {
        std::lock_guard<std::mutex> lock(mtx);  // 加锁
        return cache.empty();
    }
};

int main() {
    try {
        UpdateCaching cache(3);
        cache.put(1, 10);
        cache.put(2, 20);
        cache.put(3, 30);
        std::cout << cache.get(2) << std::endl;  // 输出 20
        cache.put(4, 40);  // 缓存已满,删除最早的元素 (1, 10)
        std::cout << cache.get(1) << std::endl;  // 输出 -1
        std::cout << "Cache size: " << cache.size() << std::endl;
        std::cout << "Is cache empty? " << (cache.empty() ? "Yes" : "No") << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }
    return 0;
}

在多线程环境下使用:

// 线程函数,用于向缓存中插入数据
void insertData(UpdateCaching& cache, int start, int end) {
    for (int i = start; i < end; ++i) {
        cache.put(i, i * 10);
    }
}

// 线程函数,用于从缓存中获取数据
void getData(UpdateCaching& cache, int start, int end) {
    for (int i = start; i < end; ++i) {
        int value = cache.get(i);
        if (value != -1) {
            std::cout << "Key: " << i << ", Value: " << value << std::endl;
        }
    }
}

int main() {
    try {
        UpdateCaching cache(10);

        // 创建插入数据的线程
        std::thread insertThread1(insertData, std::ref(cache), 0, 5);
        std::thread insertThread2(insertData, std::ref(cache), 5, 10);

        // 创建获取数据的线程
        std::thread getThread1(getData, std::ref(cache), 0, 5);
        std::thread getThread2(getData, std::ref(cache), 5, 10);

        // 等待所有线程完成
        insertThread1.join();
        insertThread2.join();
        getThread1.join();
        getThread2.join();

        std::cout << "Cache size: " << cache.size() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}
优化说明
  1. 边界条件检查:在构造函数中添加了对 capacity 参数的检查,如果传入的值不合法,会抛出 std::invalid_argument 异常。
  2. 异常处理:通过 try-catch 块捕获可能抛出的异常,并进行相应的处理,提高了代码的健壮性。
  3. 代码封装性:添加了 size()empty() 方法,方便调用者获取缓存的当前大小和检查缓存是否为空。
  4. 线程安全:使用 std::mutex 实现了简单的线程安全机制,在关键操作(如 getputsizeempty 等)前加锁,确保多线程环境下的操作安全。

DeepSeek(深度思考R1):

代码实现

#include <list>
#include <unordered_map>
#include <algorithm>

using namespace std;

class UpdateCaching {
private:
    int max_capacity;
    list<pair<int, int>> cache_list;
    unordered_map<int, list<pair<int, int>>::iterator> cache_map;

public:
    UpdateCaching(int capacity) {
        max_capacity = max(0, capacity); // 处理非法容量值
    }

    int get(int key) {
        auto it = cache_map.find(key);
        if (it == cache_map.end()) {
            return -1;
        }
        // 将访问的节点移动到链表头部
        cache_list.splice(cache_list.begin(), cache_list, it->second);
        return it->second->second;
    }

    void put(int key, int value) {
        if (max_capacity <= 0) return; // 容量为0时不操作

        auto it = cache_map.find(key);
        if (it != cache_map.end()) {
            // 更新已有值并移到头部
            it->second->second = value;
            cache_list.splice(cache_list.begin(), cache_list, it->second);
            return;
        }

        // 插入新数据
        if (cache_map.size() >= max_capacity) {
            // 删除最久未使用的数据
            int last_key = cache_list.back().first;
            cache_map.erase(last_key);
            cache_list.pop_back();
        }

        // 插入新数据到头部
        cache_list.emplace_front(key, value);
        cache_map[key] = cache_list.begin();
    }
};
实现说明:
  1. 数据结构选择

    • 使用双向链表list维护访问顺序,最近访问的节点保持在链表头部
    • 使用哈希表unordered_map实现O(1)时间复杂度的查找操作
  2. 核心操作

    • get操作
      • 查找存在时,将节点移动到链表头部并返回值
      • 不存在时返回-1
    • put操作
      • 已存在时更新值并移动到头部
      • 不存在时先检查容量,满时删除链表尾部元素
      • 插入新元素到链表头部并更新哈希表
  3. 边界处理

    • 构造函数处理非法容量值(负数自动设为0)
    • 容量为0时缓存不可用
    • 正确处理链表空时的删除操作
  4. 时间复杂度

    • get和put操作的时间复杂度均为O(1)
    • 链表操作使用splice实现节点移动,保证O(1)时间复杂度

在原有LRU缓存实现基础上,可以通过以下方案优化线程安全问题:

优化方案一:基础互斥锁版本

#include <list>
#include <unordered_map>
#include <mutex>

class UpdateCaching {
private:
    int max_capacity;
    list<pair<int, int>> cache_list;
    unordered_map<int, list<pair<int, int>>::iterator> cache_map;
    mutable mutex cache_mutex; // 可变的互斥锁

public:
    UpdateCaching(int capacity) : max_capacity(max(0, capacity)) {}

    int get(int key) {
        lock_guard<mutex> lock(cache_mutex); // 自动加锁
        if (auto it = cache_map.find(key); it != cache_map.end()) {
            cache_list.splice(cache_list.begin(), cache_list, it->second);
            return it->second->second;
        }
        return -1;
    }

    void put(int key, int value) {
        lock_guard<mutex> lock(cache_mutex);
        if (max_capacity <= 0) return;

        if (auto it = cache_map.find(key); it != cache_map.end()) {
            it->second->second = value;
            cache_list.splice(cache_list.begin(), cache_list, it->second);
            return;
        }

        if (cache_map.size() >= max_capacity) {
            cache_map.erase(cache_list.back().first);
            cache_list.pop_back();
        }

        cache_list.emplace_front(key, value);
        cache_map[key] = cache_list.begin();
    }
};

优化方案二:读写锁版本(C++17+)

#include <shared_mutex>

class UpdateCaching {
    // ...其他成员同基础版...
    mutable shared_mutex rw_mutex; // 读写锁

public:
    int get(int key) {
        shared_lock<shared_mutex> lock(rw_mutex); // 共享锁
        // ...原有逻辑...
    }

    void put(int key, int value) {
        unique_lock<shared_mutex> lock(rw_mutex); // 独占锁
        // ...原有逻辑...
    }
};

优化分析说明:

  1. 线程安全实现

    • 使用std::mutex保护所有缓存操作
    • 通过lock_guard自动管理锁的生命周期
    • 读写分离版本采用shared_mutex提升读多写少场景性能
  2. 性能影响评估

    方案读并发写并发适用场景
    普通互斥锁串行串行低并发通用场景
    读写锁并行读串行写读多写少的高并发场景
  3. 异常安全

    • 所有容器操作保证基本异常安全
    • 使用RAII风格的锁管理确保不会死锁
  4. 扩展优化建议

    // 示例:分段锁优化(适用于超高并发场景)
    class SegmentedLRUCache {
        constexpr static int SEGMENTS = 16;
        array<mutex, SEGMENTS> segment_mutexes;
        array<UpdateCaching, SEGMENTS> segments;
    
        size_t get_segment(int key) {
            return hash<int>{}(key) % SEGMENTS;
        }
    public:
        int get(int key) {
            auto seg = get_segment(key);
            lock_guard<mutex> lock(segment_mutexes[seg]);
            return segments[seg].get(key);
        }
        // 类似实现put方法...
    };
    

建议根据实际使用场景选择优化方案:

  • 常规服务:基础互斥锁方案即可满足需求
  • 高频读取服务:升级到读写锁版本
  • 超大规模并发:考虑分段锁或无锁队列方案

需要特别注意:线程安全实现会带来约10%-30%的性能损耗,在单线程场景中可以通过编译选项禁用锁机制(通过条件编译或模板策略)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2293681.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DS图(中)(19)

文章目录 前言一、图的遍历广度优先遍历深度优先遍历 二、最小生成树Kruskal算法Prim算法两种方法对比 总结 前言 承上启下&#xff0c;我们来学习下图的中篇&#xff01;&#xff01;&#xff01; 一、图的遍历 图的遍历指的是遍历图中的顶点&#xff0c;主要有 广度优先遍历 …

YK人工智能(六)——万字长文学会基于Torch模型网络可视化

1. 可视化网络结构 随着深度神经网络做的的发展&#xff0c;网络的结构越来越复杂&#xff0c;我们也很难确定每一层的输入结构&#xff0c;输出结构以及参数等信息&#xff0c;这样导致我们很难在短时间内完成debug。因此掌握一个可以用来可视化网络结构的工具是十分有必要的…

Gitea+Gridea 创建个人博客

历史文档存档&#xff0c;该方法目前已经无法使用&#xff0c;部署方法可供参考 Gitea部分 1.关于Gitea Gitea 是一个面向开源及私有软件项目的托管平台&#xff0c;是全球最大的代码托管平台之一。它采用 Git 分布式版本控制系统&#xff0c;为开发者提供了代码托管、版本控…

【Linux】一文带你入门了解线程和虚拟地址空间中页表映射的秘密(内附手绘底层逻辑图 通俗易懂)

绪论​ 每日激励&#xff1a;“努力去做自己该做的&#xff0c;但是不要期待回报&#xff0c;不是付出了就会有回报的&#xff0c;做了就不要后悔&#xff0c;不做才后悔。—Jack” 绪论​&#xff1a; 本章是LInux中非常重要的线程部分&#xff0c;通过了解线程的基本概念&am…

Flash Attention与Attention

原始Attention是&#xff1a; Flash Attention&#xff1a; 伪代码&#xff1a;4d&#xff08;分别代表Q\K\V\O&#xff09; Flash Attention2优化了

JAVA进阶之线程

为神马有线程&#xff1f;这玩意儿在干嘛&#xff1f;&#xff1f;&#xff1f; 回答这个问题&#xff0c;就先要知道一点点计算机的工作方式。 总所周知&#xff0c;计算机有五部分&#xff1a;输入输出、计算器、存储器、控制器。而在计算机内&#xff0c;CPU、内存、I/O之…

机器学习专业毕设选题推荐合集 人工智能

目录 前言 毕设选题 开题指导建议 更多精选选题 选题帮助 最后 前言 大家好,这里是海浪学长毕设专题! 大四是整个大学期间最忙碌的时光&#xff0c;一边要忙着准备考研、考公、考教资或者实习为毕业后面临的升学就业做准备,一边要为毕业设计耗费大量精力。学长给大家整理…

C++ 中的 `string` 类型:全面解析与高效操作

C 中的 string 类型&#xff1a;全面解析与高效操作 在 C 中&#xff0c;string 类型是对字符数组的高级封装&#xff0c;它提供了大量内置函数&#xff0c;使得字符串的处理变得更为简便和高效。与 C 风格的字符数组不同&#xff0c;string 类型不仅自动管理内存&#xff0c;…

Java入门进阶

文章目录 1、常用API 1.1、Math1.2、System1.3、Object1.4、Arrays1.5、基本类型包装类 1.5.1、基本类型包装类概述1.5.2、Integer1.5.3、int和String相互转换1.5.4、自动装箱和拆箱 1.6、日期类 1.6.1、Date类1.6.2、SimpleDateFormat类 1.6.2.1、格式化&#xff08;从Date到…

C#结合html2canvas生成切割图片并导出到PDF

目录 需求 开发运行环境 实现 生成HTML范例片断 HTML元素转BASE64 BASE64转图片 切割长图片 生成PDF文件 小结 需求 html2canvas 是一个 JavaScript 库&#xff0c;它可以把任意一个网页中的元素&#xff08;包括整个网页&#xff09;绘制到指定的 canvas 中&#xf…

485网关数据收发测试

目录 1.UDP SERVER数据收发测试 使用产品&#xff1a; || ZQWL-GW1600NM 产品||【智嵌物联】智能网关型串口服务器 1.UDP SERVER数据收发测试 A&#xff08;TX&#xff09;连接RX B&#xff08;RX&#xff09;连接TX 打开1个网络调试助手&#xff0c;模拟用户的UDP客户端设…

InnoDB和MyISAM的比较、水平切分和垂直切分、主从复制中涉及的三个线程、主从同步的延迟产生和解决

InnoDB和MyISAM的比较 事务支持&#xff1a; InnoDB支持&#xff1a;支持事务 (ACID 属性)。支持 Commit、Rollback 和 Savepoint 操作。适合需要事务处理的应用&#xff0c;例如银行系统。MyISAM:不支持事务。每次操作都是自动提交&#xff0c;不能回滚或中止。适合对事务要求…

JDK9新特性

文章目录 新特性&#xff1a;1.模块化系统使用模块化module-info.java&#xff1a;exports&#xff1a;opens&#xff1a;requires&#xff1a;provides&#xff1a;uses&#xff1a; 2.JShell启动Jshell执行计算定义变量定义方法定义类帮助命令查看定义的变量&#xff1a;/var…

基于Ubuntu2404搭建Zabbix7.2

Zabbix 搭建zabbix zabbix7.2已推出&#xff1a;官网 增加的新功能如下&#xff1a; 1.使用新的热门商品小部件全面概览指标 数据概览小部件已转换为热门项目小部件使用项目模式可以实现细粒度的项目选择利用条形图、指标和迷你图来可视化您的数据定义价值阈值以动态地可视化…

Math Reference Notes: 符号函数

1. 符号函数的定义 符号函数&#xff08;Sign Function&#xff09; sgn ( x ) \text{sgn}(x) sgn(x) 是一个将实数 ( x ) 映射为其 符号值&#xff08;即正数、负数或零&#xff09;的函数。 它的定义如下&#xff1a; sgn ( x ) { 1 如果 x > 0 0 如果 x 0 − 1 如…

【数据结构】链表应用-链表重新排序

重新排序 反转链表预期实现思路解题过程code力扣代码核心代码完整代码 总结 删除链表中间节点代码解惑 链表重新排序题目描述解题思路解题过程复杂度代码力扣代码完整代码 反转链表 预期实现 思路 你选用何种方法解题&#xff1f; 我选用了迭代法来反转链表。这是一种经典且高…

学习threejs,pvr格式图片文件贴图

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️PVR贴图1.2 ☘️THREE.Mesh…

2022年全国职业院校技能大赛网络系统管理赛项模块A:网络构建(样题2)-网络部分解析-附详细代码

目录 附录1:拓扑图​编辑 附录2:地址规划表 1.SW1 2.SW2 3.SW3 4.SW4 5.SW5 6.SW6 7.SW7 8.R1 9.R2 10.R3 11.AC1 12.AC2 13.EG1 14.EG2 15.AP2 16.AP3 附录1:拓扑图 附录2:地址规划表

C++,STL,【目录篇】

文章目录 一、简介二、内容提纲第一部分&#xff1a;STL 概述第二部分&#xff1a;STL 容器第三部分&#xff1a;STL 迭代器第四部分&#xff1a;STL 算法第五部分&#xff1a;STL 函数对象第六部分&#xff1a;STL 高级主题第七部分&#xff1a;STL 实战应用 三、写作风格四、…

【AI论文】直接对齐算法之间的差异模糊不清

摘要&#xff1a;直接对齐算法&#xff08;DAAs&#xff09;通过在对齐人类反馈的强化学习&#xff08;RLHF&#xff09;中用直接策略优化替代强化学习&#xff08;RL&#xff09;和奖励建模&#xff08;RM&#xff09;&#xff0c;简化了语言模型对齐过程。DAAs可以根据其排序…