LRU是Least Recently Used的縮寫,意思是最近最少使用,它是一種Cache替換算法。 什麼是Cache?狹義的Cache指的是位於CPU和主存間的快速RAM, 一般它不像系統主存那樣使用DRAM技術,而使用昂貴但較快速的SRAM技術。 廣義上的Cache指的是位於速度相差較大的兩種硬件之間, 用於協調二者數據傳輸速度差別的結構。除了CPU與主存之間有Cache, 內存與硬盤之間也有Cache,乃至在硬盤與網絡之間也有某種意義上的Cache── 稱爲Internet臨時文件夾或網絡內容緩存等。html
Cache的容量有限,所以當Cache的容量用完後,而又有新的內容須要添加進來時, 就須要挑選並捨棄原有的部份內容,從而騰出空間來放新內容。LRU Cache 的替換原則就是將最近最少使用的內容替換掉。其實,LRU譯成最久未使用
會更形象, 由於該算法每次替換掉的就是一段時間內最久沒有使用過的內容。node
LRU的典型實現是hash map + doubly linked list
, 雙向鏈表用於存儲數據結點,而且它是按照結點最近被使用的時間來存儲的。 若是一個結點被訪問了, 咱們有理由相信它在接下來的一段時間被訪問的機率要大於其它結點。因而, 咱們把它放到雙向鏈表的頭部。當咱們往雙向鏈表裏插入一個結點, 咱們也有可能很快就會使用到它,一樣把它插入到頭部。 咱們使用這種方式不斷地調整着雙向鏈表,鏈表尾部的結點天然也就是最近一段時間, 最久沒有使用到的結點。那麼,當咱們的Cache滿了, 須要替換掉的就是雙向鏈表中最後的那個結點(不是尾結點,頭尾結點不存儲實際內容)。ios
以下是雙向鏈表示意圖,注意頭尾結點不存儲實際內容:c++
頭 --> 結 --> 結 --> 結 --> 尾 結 點 點 點 結 點 <-- 1 <-- 2 <-- 3 <-- 點
假如上圖Cache已滿了,咱們要替換的就是結點3。算法
哈希表的做用是什麼呢?若是沒有哈希表,咱們要訪問某個結點,就須要順序地一個個找, 時間複雜度是O(n)。使用哈希表可讓咱們在O(1)的時間找到想要訪問的結點, 或者返回未找到。緩存
Cache主要有兩個接口:網絡
T Get(K key); void Put(K key, T data);
當咱們經過鍵值來訪問類型爲T的數據時,調用Get函數。若是鍵值爲key的數據已經在 Cache中,那就返回該數據,同時將存儲該數據的結點移到雙向鏈表頭部。 若是咱們查詢的數據不在Cache中,咱們就能夠經過Put接口將數據插入雙向鏈表中。 若是此時的Cache還沒滿,那麼咱們將新結點插入到鏈表頭部, 同時用哈希表保存結點的鍵值及結點地址對。若是Cache已經滿了, 咱們就將鏈表中的最後一個結點(注意不是尾結點)的內容替換爲新內容, 而後移動到頭部,更新哈希表。數據結構
注意,hash map並非C++標準的一部分,我使用的是Linux下g++ 4.6.1, hash_map放在/usr/include/c++/4.6/ext下,須要使用__gnu_cxx
名空間, Linux平臺能夠切換到c++的include目錄:cd /usr/include/c++/版本 而後grep -iR 「hash_map」 ./ 查看在哪一個文件中,通常頭文件的最後幾行會提示它所在的名空間。 其它平臺請自行探索。XD函數
固然若是你已經很fashion地在使用C++ 11,就不會有這些小困擾了。spa
// A simple LRU cache written in C++ // Hash map + doubly linked list #include <iostream> #include <vector> #include <ext/hash_map> using namespace std; using namespace __gnu_cxx; template <class K, class T> struct Node{ K key; T data; Node *prev, *next; }; template <class K, class T> class LRUCache{ public: LRUCache(size_t size){ entries_ = new Node<K,T>[size]; for(int i=0; i<size; ++i)// 存儲可用結點的地址 free_entries_.push_back(entries_+i); head_ = new Node<K,T>; tail_ = new Node<K,T>; head_->prev = NULL; head_->next = tail_; tail_->prev = head_; tail_->next = NULL; } ~LRUCache(){ delete head_; delete tail_; delete[] entries_; } void Put(K key, T data){ Node<K,T> *node = hashmap_[key]; if(node){ // node exists detach(node); node->data = data; attach(node); } else{ if(free_entries_.empty()){// 可用結點爲空,即cache已滿 node = tail_->prev; detach(node); hashmap_.erase(node->key); } else{ node = free_entries_.back(); free_entries_.pop_back(); } node->key = key; node->data = data; hashmap_[key] = node; attach(node); } } T Get(K key){ Node<K,T> *node = hashmap_[key]; if(node){ detach(node); attach(node); return node->data; } else{// 若是cache中沒有,返回T的默認值。與hashmap行爲一致 return T(); } } private: // 分離結點 void detach(Node<K,T>* node){ node->prev->next = node->next; node->next->prev = node->prev; } // 將結點插入頭部 void attach(Node<K,T>* node){ node->prev = head_; node->next = head_->next; head_->next = node; node->next->prev = node; } private: hash_map<K, Node<K,T>* > hashmap_; vector<Node<K,T>* > free_entries_; // 存儲可用結點的地址 Node<K,T> *head_, *tail_; Node<K,T> *entries_; // 雙向鏈表中的結點 }; int main(){ hash_map<int, int> map; map[9]= 999; cout<<map[9]<<endl; cout<<map[10]<<endl; LRUCache<int, string> lru_cache(100); lru_cache.Put(1, "one"); cout<<lru_cache.Get(1)<<endl; if(lru_cache.Get(2) == "") lru_cache.Put(2, "two"); cout<<lru_cache.Get(2); return 0; }