实现一个 O(1) 查找的 LRU Cache

24 篇文章 1 订阅
6 篇文章 1 订阅

前几天百度面试,当时让实现一个 LRU Cache,要求 O(1) 完成查找。后来发现这个也可以用在自己简易的 key-value 数据库项目中。

简单来说 LRU 是内存管理的一种算法,淘汰最近不用的页。

O(1) 时间完成查找,那除了 hash 别无选择。LRU 用双向链表实现即可。数据结构选择好了,查找我们用 hash 通过 key 得到链表节点的位置,然后更新 LRU 链表即可。

简单说下自己的项目,一个类似 Memcache 的小型数据库。

当时和 Memcache 设计不同的一点是没有将所有 Slab 上的 item 串到一起。而是将 Slab 串成一个链,每个 Slab 有自己的 Free Item List 和 Alloc Item List,当前 Slab 锁如果被占用,自旋若干次,还失败则到下一个 Slab 尝试获取锁,避免阻塞。这样减小锁的颗粒度,在并发情况下性能应该会好些。每个 Slab 的 Alloc Item List 就是一个 LRU Cache。若 Free Item 分配完时,则通过 hashmap 来找到 Alloc Item List 上的节点,完成 LRU 策略。

看代码吧,这里简单的将 Item 设置成key value 都为 string 的一个 pair。

hodis_LRU.h


#include <iostream>
#include <list>
#include <map>
#include <unordered_map>
#include <memory>
#include <string>
#include <mutex>
#include <utility>

namespace hodis {

class LRUCache {
    public:
        using Item = std::pair<std::string, std::string>;
        using Iter = std::list<Item>::iterator;
        using LRUCacheList = std::shared_ptr<std::list<Item>>;
        using LRUHashMap   = std::shared_ptr<std::unordered_map<std::string, Iter>>;

        LRUCache();
        LRUCache(uint64_t size);
        ~LRUCache();

    public:
        void set(const std::string &key, const std::string &value);       
        std::string get(const std::string &key);
        void foreach();

    private:
        /* LRUCache List */
        LRUCacheList lruCache;
        /* hashmap 
         * key:string 
         * value:item* 
         * */
        LRUHashMap hashMap;
        uint64_t lruCacheCurSize;
        uint64_t lruCacheMaxSize;
};

} /* hodis */

hodis_LRU.cpp


#include "hodis_LRU.h"

namespace hodis {

LRUCache::LRUCache(uint64_t size) {
    /* 创建 LRU 链表和 hashmap */
    lruCache = std::make_shared<std::list<Item>>();
    hashMap  = std::make_shared<std::unordered_map<std::string, Iter>>();
    lruCacheCurSize = 0;
    lruCacheMaxSize = size;
}

LRUCache::~LRUCache() {

}

void
LRUCache::set(const std::string &key, const std::string &value){
    std::mutex mutex;
    auto item = std::make_pair(key, value);
    /* 加锁 */
    std::lock_guard<std::mutex> lock(mutex);
    /* 如果当前 size 小于最大 size 则不用淘汰
     * 仅仅更新 LRU 链表即可
     * 如果当前 size 大于等于最大 size 则需要淘汰和更新 
     * */
    if (lruCacheCurSize < lruCacheMaxSize) {
        lruCacheCurSize++;
        lruCache->insert(lruCache->begin(), item);
        hashMap->insert({key, lruCache->begin()});
    }else {
        /* erase last item */
        lruCache->erase(--lruCache->end());
        lruCache->insert(lruCache->begin(), item);
        if (hashMap->find(key) != hashMap->end()) {
            (*hashMap)[key] = lruCache->begin();
        }else {
            hashMap->insert({key, lruCache->begin()});
        }
    }
}

std::string 
LRUCache::get(const std::string &key) {
    std::mutex mutex;
    /* 加锁 */
    std::lock_guard<std::mutex> lock(mutex);
    if (hashMap->find(key) != hashMap->end()) {
        auto iter = (*hashMap)[key];      
        auto item = *iter;

        /* 注意:删除节点时,当前迭代器会失效
         * 其余迭代器不变 */
        lruCache->erase(iter);
        lruCache->insert(lruCache->begin(), std::move(item));
        (*hashMap)[key] = lruCache->begin();

        return (*(*hashMap)[key]).second;   
    }else {
        return "";
    }
}

void
LRUCache::foreach() {
    for (auto &item : *lruCache) {
        std::cout << "key:" << item.first << " value:" << item.second << std::endl;
    }
    std::cout << std::endl;
}

} /* hodis */

测试代码:

#include <iostream>
#include "hodis_LRU.h"

int main() {
    hodis::LRUCache lruCache(5);

    lruCache.set("1", "1");
    lruCache.set("2", "2");
    lruCache.set("3", "3");
    lruCache.set("4", "4");
    lruCache.set("5", "5");

    lruCache.foreach();
    /* 新加节点,大于 size 执行 LRU 策略 */
    lruCache.set("6", "6");
    lruCache.foreach();
    lruCache.set("5", "5");
    lruCache.foreach();

    auto s1 = lruCache.get("5");
    auto s2 = lruCache.get("10");

    std::cout << s1 << std::endl;
    std::cout << s2 << std::endl;
}

运行结果如下:

当到达 LRU Max Size 时,进行 LRU 算法。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: 使用Python编写LRU缓存算法的一个简单实现可以如下:def lru_cache(maxsize): cache = {} queue = [] def wrapper(func): def inner(*args, **kwargs): key = str(args) + str(kwargs) if key in cache: return cache[key] result = func(*args, **kwargs) if len(queue) >= maxsize: del cache[queue.pop(0)] cache[key] = result queue.append(key) return result return inner return wrapper使用Python编写LRU缓存算法的一个简单实现可以如下:def lru_cache(maxsize): cache = {} queue = [] def wrapper(func): def inner(*args, **kwargs): key = str(args) + str(kwargs) if key in cache: return cache[key] result = func(*args, **kwargs) if len(queue) >= maxsize: del cache[queue.pop(0)] cache[key] = result queue.append(key) return result return inner return wrapper答:使用Python编写LRU缓存算法的一个简单实现可以如下:def lru_cache(maxsize): cache = {} queue = [] def wrapper(func): def inner(*args, **kwargs): key = str(args) + str(kwargs) if key in cache: return cache[key] result = func(*args, **kwargs) if len(queue) >= maxsize: del cache[queue.pop(0)] cache[key] = result queue.append(key) return result return inner return wrapper用Python编写LRU缓存算法是可行的,通过使用上面提供的简单实现可以实现这一功能。 ### 回答2: LRU(Least Recently Used)算法是一种常用的缓存替换策略,它根据数据节点的使用顺序来决定是否将数据从缓存中删除。当缓存已满,需要替换一个节点时,LRU算法选择最近最少使用的数据节点进行替换。 下面是通过Python实现一个简单的LRU Cache算法: ```python class LRUCache: def __init__(self, capacity): self.capacity = capacity self.cache = {} self.keys = [] def get(self, key): if key in self.cache: self.keys.remove(key) self.keys.append(key) # 更新使用顺序 return self.cache[key] else: return -1 def put(self, key, value): if key in self.cache: self.keys.remove(key) self.keys.append(key) # 更新使用顺序 self.cache[key] = value else: if len(self.keys) >= self.capacity: # 删除最近最少使用的数据节点 del_key = self.keys[0] del self.cache[del_key] self.keys = self.keys[1:] self.keys.append(key) # 更新使用顺序 self.cache[key] = value ``` 在这个实现中,我们使用一个字典 `cache` 来保存数据节点的键值对,并使用列表 `keys` 来记录数据节点的使用顺序。`capacity` 参数指定了缓存的最大容量。 在 `get` 方法中,如果需要获取的 `key` 存在于缓存中,我们首先从 `keys` 列表中移除 `key`,然后将其添加到列表的末尾,以表示最近使用过。然后返回对应的值。如果 `key` 不存在于缓存中,返回 -1。 在 `put` 方法中,如果需要插入的 `key` 已经存在于缓存中,我们同样要将其移除并重新添加到 `keys` 列表的末尾,并更新对应的值。否则,如果缓存已满,我们删除 `keys` 列表的第一个元素,然后从 `cache` 字典中删除对应的键值对。最后,将新的 `key` 和 `value` 添加到 `keys` 列表和 `cache` 字典中。 这样,我们就实现一个简单的LRU Cache算法。 ### 回答3: LRU缓存(Least Recently Used,最近最少使用)是一种常用的缓存算法,它根据数据的使用历史来决定哪些数据应该保留在缓存中。当缓存达到最大容量时,如果有新数据要放入缓存中,那么就需要删除最久未使用的数据。 下面是用Python实现LRU缓存算法的示例代码: ```python class LRUCache: def __init__(self, capacity: int): self.capacity = capacity self.cache = {} self.order = [] def get(self, key: int) -> int: if key in self.cache: self.order.remove(key) self.order.append(key) return self.cache[key] else: return -1 def put(self, key: int, value: int) -> None: if key in self.cache: self.order.remove(key) elif len(self.cache) >= self.capacity: oldest_key = self.order.pop(0) del self.cache[oldest_key] self.cache[key] = value self.order.append(key) ``` 以上代码中,LRUCache类是实现LRU缓存算法的主要类。它有三个主要成员变量:capacity表示缓存的容量,cache一个字典,用于存储缓存数据,order是一个列表,用于记录数据的访问顺序。 `get`方法用于从缓存中获取指定的键对应的值。如果键存在于缓存中,就将该键移到最后,表示最近使用过,然后返回该键对应的值;否则返回-1。 `put`方法用于向缓存添加新的键值对。如果键已经存在于缓存中,就将该键移到最后,表示最近使用过,然后更新该键对应的值;如果缓存已满,就删除最久未使用的键值对;最后在缓存添加新的键值对,并将该键添加到order列表的最后。 这样通过LRUCache类,我们可以轻松实现一个LRU缓存,并且保证缓存的容量不会被超出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏天的技术博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值