這個是比較經典的LRU(Least recently used,最近最少使用)算法,算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是「若是數據最近被訪問過,那麼未來被訪問的概率也更高」。 通常應用在緩存替換策略中。其中的」使用」包括訪問get和更新set。html
LRU算法java
LRU是Least Recently Used 近期最少使用算法。內存管理的一種頁面置換算法,對於在內存中但又不用的數據快(內存塊)叫作LRU,Oracle會根據那些數據屬於LRU而將其移出內存而騰出空間來加載另外的數據,通常用於大數據處理的時候不多使用的數據那麼就直接請求數據庫,若是常常請求的數據就直接在緩存裏面讀取。node
最近最久未使用(LRU)的頁面置換算法,是根據頁面調入內存後的使用狀況進行決策的。因爲沒法預測各頁面未來的使用狀況,只能利用「最近的過去」做爲「最近的未來」的近似,所以,LRU置換算法是選擇最近最久未使用的頁面予以淘汰。該算法賦予每一個頁面一個訪問字段,用來記錄一個頁面自上次被訪問以來所經歷的時間t,當須淘汰一個頁面時,選擇現有頁面中其t值最大的,即最近最久未使用的頁面予以淘汰(可使用這種方法去實現)。ios
LRU的實現算法
1) 可利用一個棧來保存當前使用的各個頁面的頁面號。每當進程訪問某頁面時,便將該頁面的頁面號從棧中移出,將它壓入棧頂。所以,棧頂始終是最新被訪問頁面的編號,而棧底則是最近最久未使用頁面的頁面號。(因爲效率太低在leetCode上超時,代碼未貼出)數據庫
2) 也能夠過雙向鏈表和HashMap來實現。緩存
雙向鏈表用於存儲數據結點,而且它是按照結點最近被使用的時間來存儲的。 若是一個結點被訪問了, 咱們有理由相信它在接下來的一段時間被訪問的機率要大於其它結點。因而, 咱們把它放到雙向鏈表的頭部。當咱們往雙向鏈表裏插入一個結點, 咱們也有可能很快就會使用到它,一樣把它插入到頭部。 咱們使用這種方式不斷地調整着雙向鏈表,鏈表尾部的結點天然也就是最近一段時間, 最久沒有使用到的結點。那麼,當咱們的Cache滿了, 須要替換掉的就是雙向鏈表中最後的那個結點(不是尾結點,頭尾結點不存儲實際內容)。post
以下是雙向鏈表示意圖,注意頭尾結點不存儲實際內容:大數據
頭 --> 結 --> 結 --> 結 --> 尾 結 點 點 點 結 點 <-- 1 <-- 2 <-- 3 <-- 點
假如上圖Cache已滿了,咱們要替換的就是結點3。spa
哈希表的做用是什麼呢?若是沒有哈希表,咱們要訪問某個結點,就須要順序地一個個找, 時間複雜度是O(n)。使用哈希表可讓咱們在O(1)的時間找到想要訪問的結點, 或者返回未找到。
java實現
LinkedHashMap剛好是經過雙向鏈表實現的java集合類,它的一大特色是,以當某個位置被命中,它就會經過調整鏈表的指向,將該位置調整到頭位置,新加入的內容直接放在鏈表頭,如此一來,最近被命中的內容就向鏈表頭移動,須要替換時,鏈表最後的位置就是最近最少使用的位置。關於 LinkedHashMap 的具體實現,能夠參考此文:LinkedHashMap的實現原理。
假定現有一進程所訪問的頁面序列爲:
4,7,0,7,1,0,1,2,1,2,6
隨着進程的訪問,棧中頁面號的變化狀況如圖所示。在訪問頁面6時發生了缺頁,此時頁面4是最近最久未被訪問的頁,應將它置換出去。
題目的要求是實現下面三個方法:
class LRUCache{ public: LRUCache(int capacity) { } int get(int key) { } void set(int key, int value) { } };
C++實現
// 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; }
參考:http://hawstein.com/posts/lru-cache-impl.html;http://www.cnblogs.com/LZYY/p/3447785.html