C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++ LRU缓存

C++实现LRU缓存的操作方法

作者:吃小南瓜

LRU是一种常用的缓存淘汰策略,主要目的是在缓存空间有限的情况下,优先淘汰那些最长时间没有被访问的数据项,这篇文章主要介绍了C++实现LRU缓存,需要的朋友可以参考下

LRU的概念

LRU(Least Recently Used,最近最少使用)是一种常用的缓存淘汰策略,主要目的是在缓存空间有限的情况下,优先淘汰那些最长时间没有被访问的数据项。LRU 策略的核心思想是:

在实现LRU缓存时,通常会使用数据结构如哈希表双向链表。哈希表用于快速定位缓存中的数据项,而双向链表则用于维护数据项的访问顺序。每次访问数据项时,都会将其移动到链表的头部,表示它是最近被访问的。当需要淘汰数据时,直接从链表的尾部开始淘汰即可。

LRU策略在许多场景中都非常有用,比如操作系统的页面置换、数据库的查询缓存、Web服务器的页面缓存等。它可以帮助系统更有效地利用有限的缓存资源,提高系统的整体性能。
别急,我们先学实现LRU要用的哈希表双向链表

哈希表(unordered_map)

在C++中,unordered_map 是标准模板库(STL)中的一个关联容器,它基于哈希表的实现。它存储了键值对,允许通过键快速访问和修改值。unordered_map 提供了平均常数时间复杂度的访问、插入和删除操作。

主要特性

常用操作

以下是使用 unordered_map 的一个简单示例:

#include <iostream>
#include <unordered_map>
int main() {
    // 创建一个 unordered_map,键为 int,值为 string
    unordered_map<int, string> umap;
    // 插入元素
    umap[1] = "one";
    umap[2] = "two";
    umap[3] = "three";
    // 访问并打印元素
    for (const auto& pair : umap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    // 访问特定键的值
    try {
        std::cout << "Value for key 2: " << umap.at(2) << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << e.what() << std::endl;
    }
    // 查找键是否存在
    auto it = umap.find(3);
    if (it != umap.end()) {
        std::cout << "Key 3 found, value: " << it->second << std::endl;
    }
    // 删除元素
    umap.erase(2);
    std::cout << "After erasing key 2:" << std::endl;
    for (const auto& pair : umap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    return 0;
}

输出:

1: one
2: two
3: three
Value for key 2: two
Key 3 found, value: three
After erasing key 2:
1: one
3: three

在这个示例中:

双向链表(list)

在C++中,list 是标准模板库(STL)中的一个容器类,它提供了双向链表的实现。与数组或向量(vector)不同,list 允许在任意位置高效地插入和删除元素,而不需要移动其他元素。

以下是 list 的一些主要特性和常用操作:

特性

常用操作

示例代码

以下是使用 list 的一个简单示例:

#include <iostream>
#include <list>
int main() {
    list<int> myList;
    // 向链表中添加元素
    myList.push_back(10);
    myList.push_back(20);
    myList.push_front(5);
    // 访问并打印链表中的元素
    for (list<int>::iterator it = myList.begin(); it != myList.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    // 删除头部元素
    myList.pop_front();
    std::cout << "After popping front: ";
    for (auto it = myList.begin(); it != myList.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    // 删除尾部元素
    myList.pop_back();
    std::cout << "After popping back: ";
    for (auto it = myList.begin(); it != myList.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    return 0;
}

输出:

5 10 20 
After popping front: 10 20 
After popping back: 10 

在这个示例中,我们创建了一个 list 并添加了一些整数元素。然后,我们遍历并打印链表中的元素,删除头部和尾部的元素,并再次打印链表中的元素。

到这里,你已经掌握实现LRU缓存的两个条件了,马上你就要成功了!!!

真的,不信你往下看!

LRU缓存(C++)

#include <iostream>
#include <list>
#include <unordered_map>
// 使用 using namespace std; 来简化代码,避免重复书写 std:: 前缀
using namespace std;
// LRUCache 类定义
class LRUCache {
private:
    int capacity;  // 缓存的容量
    list<int> keys;  // 使用双向链表存储键,保持访问顺序
    unordered_map<int, pair<int, list<int>::iterator>> cache;  // 存储键值对和对应的链表迭代器
public:
    // 构造函数,初始化缓存容量
    LRUCache(int capacity) : capacity(capacity) {}
    // 获取缓存中键对应的值
    int get(int key) {
        auto it = cache.find(key);
        if (it == cache.end()) {
            return -1;  // 如果键不存在,返回 -1
        }
        // 更新访问顺序,将该键移动到链表头部
        keys.erase(it->second.second);
        keys.push_front(key);
        it->second.second = keys.begin();
        return it->second.first;  // 返回键对应的值
    }
    // 插入或更新缓存中的键值对
    void put(int key, int value) {
        if (cache.size() >= capacity && cache.find(key) == cache.end()) {
            // 如果缓存已满且键不存在,淘汰最不常用的键(链表尾部的键)
            auto last = keys.back();
            cache.erase(cache.find(last));
            keys.pop_back();
        }
        // 插入或更新键值对,并更新访问顺序
        cache[key] = {value, keys.insert(keys.begin(), key)};
    }
};
int main() {
    // 创建一个容量为 2 的 LRU 缓存
    LRUCache cache(2);
    // 插入键值对 (1, 1)
    cache.put(1, 1);
    // 访问键 1,输出其值
    cout << "get(1) = " << cache.get(1) << endl; // 返回 1
    // 插入键值对 (2, 2)
    cache.put(2, 2);
    // 访问键 2,输出其值
    cout << "get(2) = " << cache.get(2) << endl; // 返回 2
    // 插入键值对 (3, 3),由于缓存已满,键 1 被淘汰
    cache.put(3, 3);
    // 访问键 1,由于已被淘汰,返回 -1
    cout << "get(1) = " << cache.get(1) << endl; // 返回 -1
    // 访问键 3,输出其值
    cout << "get(3) = " << cache.get(3) << endl; // 返回 3
    // 插入键值对 (4, 4),由于缓存已满,键 2 被淘汰
    cache.put(4, 4);
    // 访问键 1,由于已被淘汰,返回 -1
    cout << "get(1) = " << cache.get(1) << endl; // 返回 -1
    // 访问键 3,输出其值
    cout << "get(3) = " << cache.get(3) << endl; // 返回 3
    // 访问键 2,由于已被淘汰,返回 -1
    cout << "get(2) = " << cache.get(2) << endl; // 返回 -1
    // 访问键 4,输出其值
    cout << "get(4) = " << cache.get(4) << endl; // 返回 4
    return 0;
}

这段代码首先定义了一个 LRUCache 类,该类使用 unordered_maplist 来实现 LRU 缓存机制。get 方法用于获取缓存中的值,如果键存在,则返回其值并更新访问顺序;如果键不存在,则返回 -1。put 方法用于插入或更新缓存中的键值对,如果缓存已满,则淘汰最不常用的键(链表尾部的键)。在 main 函数中,创建了一个 LRUCache 对象并进行了一些操作来演示其功能。

什么?看不懂?没关系,结合下面的过程看,你应该就明白了!

初始化状态

Cache: {}
Keys: []

执行 cache.put(1, 1)

Cache: {1: (1, it1)}
Keys: [1]

执行 cache.put(2, 2)

Cache: {1: (1, it1), 2: (2, it2)}
Keys: [2, 1]  (2 最近使用,1 最少使用)

执行 cache.put(3, 3)

缓存已满,淘汰键 1

Cache: {2: (2, it2), 3: (3, it3)}
Keys: [3, 2]  (3 最近使用,2 次之)

执行 cache.get(1)

键 1 不存在,返回 -1

Cache: {2: (2, it2), 3: (3, it3)}
Keys: [3, 2]

执行 cache.get(3)

键 3 存在,返回 3,并更新为最近使用

Cache: {2: (2, it2), 3: (3, it3)}
Keys: [3, 2]

执行 cache.put(4, 4)

缓存已满,淘汰键 2

Cache: {3: (3, it3), 4: (4, it4)}
Keys: [4, 3]  (4 最近使用,3 次之)

执行 cache.get(1)

键 1 不存在,返回 -1

Cache: {3: (3, it3), 4: (4, it4)}
Keys: [4, 3]

执行 cache.get(3)

键 3 存在,返回 3,并更新为最近使用

Cache: {3: (3, it3), 4: (4, it4)}
Keys: [3, 4]

执行 cache.get(2)

键 2 不存在,返回 -1

Cache: {3: (3, it3), 4: (4, it4)}
Keys: [3, 4]

执行 cache.get(4)

键 4 存在,返回 4,并更新为最近使用

Cache: {3: (3, it3), 4: (4, it4)}
Keys: [4, 3]

至此,你就算没有台明白,也一定了解LRU了。收藏可以方便下次巩固哦!!!!

到此这篇关于C++实现LRU缓存的文章就介绍到这了,更多相关C++ LRU缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文