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

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

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

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

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

实现一个 O(1) 查找的 LRU Cache_第1张图片

当时和 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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

namespace hodis {

class LRUCache {
    public:
        using Item = std::pair<std::string, std::string>;
        using Iter = std::list::iterator;
        using LRUCacheList = std::shared_ptr<std::list>;
        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>();
    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 
#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;
}

运行结果如下:

实现一个 O(1) 查找的 LRU Cache_第2张图片

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

你可能感兴趣的:(C++,操作系统,数据结构与算法,leecode)