最近偶然需要使用LRU緩存,搜索了一下,發現STL和boost裏沒有現成的。於是就用map和list簡單實現了下。通過了單元測試。因爲邏輯蠻簡單的(加上滿滿註釋不到100行),應該不會有錯哈。歡迎大家體驗。源碼除了std外,只使用了boost::function,如果不想使用boost類庫,完全可以用函數指針替換掉(註釋中有說明),但是那麼做要傳入類的成員函數就麻煩了嘿。
LRU最簡單的實現方法,應該就是隻用一個map,然後保存每個key的對應數據和訪問時間,當容量滿的時候,找到最久未訪問的數據刪除之,插入新的。這麼做簡單是簡單,但是效率不太高。下文的實現是採用一個list保存key的訪問順序(越近訪問的在越後面),這樣最老的數據就是list.front(),無須遍歷所有記錄。比較麻煩的是,當訪問某個key的時候,需要把這個key的訪問記錄移動到list的最後,還好list提供了slice函數,可以比較高效地搞定這件事情。
推薦一篇文章:http://timday.bitbucket.org/lru.html,下文的實現也有參考此文。
---------------------------代碼的分割線------------------------------------
#pragma once
#include <map>
#include <list>
#include <boost/function.hpp>
// 此頭文件包含XASSERT宏的定義,使用時請替換成自己的斷言函數
#include "xassert.h"
template < typename KeyType, typename ValueType >
class LRUCache
{
public:
// 當Cache miss的時候,獲取數據的函數
// 如果不使用boost類庫,可替換成函數指針: typedef ValueType (*FetchDataFunc)(const KeyType&)
typedef boost::function<ValueType (const KeyType&)> FetchDataFunc;
// 構造函數
// func: 當Cache miss的時候,獲取數據的函數
// capacity: Cache的容量,越大內存使用越多,也越容易命中
LRUCache(FetchDataFunc func_, size_t capacity_)
: fetchDataFunc(func_)
, capacity(capacity_)
{
XASSERT(capacity != 0);
}
// 類型定義,使用者可忽略
typedef std::list < KeyType > KeyVisitedList;
typedef typename KeyVisitedList::iterator KeyVisitedListIterator;
typedef std::map < KeyType, std::pair < ValueType, typename KeyVisitedList::iterator > > KeyValueMap;
typedef typename KeyValueMap::iterator KeyValueMapIterator;
// 獲取數據,如果Cache miss,會調用獲取數據的函數去獲取,否則直接返回Cache中的數據
ValueType& operator[](const KeyType& k)
{
// 查找Cache
KeyValueMapIterator it = keyValueMap.find(k);
if (it == keyValueMap.end())
{
// Cache失效,獲取數據插入Cache
it = insert(k, fetchDataFunc(k));
}
else
{
// Cache命中,將該key在訪問列表中的位置移動至最後面
keyVisitedList.splice(keyVisitedList.end(), keyVisitedList, (*it).second.second);
}
return (*it).second.first;
}
private:
// 在Cache中插入一條記錄
KeyValueMapIterator insert(const KeyType& k, const ValueType& v)
{
// 如果Cache已滿,刪除最久未被使用的元素
if (keyValueMap.size() == capacity)
{
eraseOldestRecord();
}
// 新插入的記錄剛被訪問,所以排在訪問列表的最後
KeyVisitedListIterator it = keyVisitedList.insert(keyVisitedList.end(), k);
LRU最簡單的實現方法,應該就是隻用一個map,然後保存每個key的對應數據和訪問時間,當容量滿的時候,找到最久未訪問的數據刪除之,插入新的。這麼做簡單是簡單,但是效率不太高。下文的實現是採用一個list保存key的訪問順序(越近訪問的在越後面),這樣最老的數據就是list.front(),無須遍歷所有記錄。比較麻煩的是,當訪問某個key的時候,需要把這個key的訪問記錄移動到list的最後,還好list提供了slice函數,可以比較高效地搞定這件事情。
推薦一篇文章:http://timday.bitbucket.org/lru.html,下文的實現也有參考此文。
---------------------------代碼的分割線------------------------------------
#pragma once
#include <map>
#include <list>
#include <boost/function.hpp>
// 此頭文件包含XASSERT宏的定義,使用時請替換成自己的斷言函數
#include "xassert.h"
template < typename KeyType, typename ValueType >
class LRUCache
{
public:
// 當Cache miss的時候,獲取數據的函數
// 如果不使用boost類庫,可替換成函數指針: typedef ValueType (*FetchDataFunc)(const KeyType&)
typedef boost::function<ValueType (const KeyType&)> FetchDataFunc;
// 構造函數
// func: 當Cache miss的時候,獲取數據的函數
// capacity: Cache的容量,越大內存使用越多,也越容易命中
LRUCache(FetchDataFunc func_, size_t capacity_)
: fetchDataFunc(func_)
, capacity(capacity_)
{
XASSERT(capacity != 0);
}
// 類型定義,使用者可忽略
typedef std::list < KeyType > KeyVisitedList;
typedef typename KeyVisitedList::iterator KeyVisitedListIterator;
typedef std::map < KeyType, std::pair < ValueType, typename KeyVisitedList::iterator > > KeyValueMap;
typedef typename KeyValueMap::iterator KeyValueMapIterator;
// 獲取數據,如果Cache miss,會調用獲取數據的函數去獲取,否則直接返回Cache中的數據
ValueType& operator[](const KeyType& k)
{
// 查找Cache
KeyValueMapIterator it = keyValueMap.find(k);
if (it == keyValueMap.end())
{
// Cache失效,獲取數據插入Cache
it = insert(k, fetchDataFunc(k));
}
else
{
// Cache命中,將該key在訪問列表中的位置移動至最後面
keyVisitedList.splice(keyVisitedList.end(), keyVisitedList, (*it).second.second);
}
return (*it).second.first;
}
private:
// 在Cache中插入一條記錄
KeyValueMapIterator insert(const KeyType& k, const ValueType& v)
{
// 如果Cache已滿,刪除最久未被使用的元素
if (keyValueMap.size() == capacity)
{
eraseOldestRecord();
}
// 新插入的記錄剛被訪問,所以排在訪問列表的最後
KeyVisitedListIterator it = keyVisitedList.insert(keyVisitedList.end(), k);