C++實現最基本的LRUCache服務器緩存

目錄:html

1、介紹;node

2、數據結構;python

3、主要的兩個函數接口Put()和Get();ios

4、C++代碼實現;算法

後臺開發必備知識,不過我不是搞這個的,只是由於好久之前就想寫這些東西,事情多,拖到如今。寫的過程裏面發現不少問題,不會所有說,最後會順帶提一提。數據庫

注意,本篇筆記只是對接口寫法作了記錄,並無進行更嚴格的設計和限制,包括更嚴密的封裝,這裏只是學習它實現的原理。ubuntu

不過有些idea仍是要知道的,系統定時對緩存進行清除並加入知足條件的新數據,是根據:訪問時間,訪問次數,可用緩存容量(分配到的內存)等因素決定的,實際設計其實不少東西須要考慮。緩存

1、介紹:

LRU,Least Recently Used,最近最少使用,服務器緩存經常使用算法的一種。服務器

好比說一些系統登陸的操做,不可能每次你訪問系統都去調用數據庫的東西,若是能劃出一些空間來,好比說500M,用來緩存這些東西,這樣用戶訪問的時候先在緩存裏找,找不到,再去訪問數據庫,同時把被訪問的內容放到緩存裏面(咱們能夠假設這些東西還會常常被訪問)。然而,咱們分配用來作緩存(Cache)的空間確定是有限的,總不可能從數據庫讀的東西所有放到緩存裏,因此,當緩存裏的內容達到上限值的時候,咱們就要把最少使用的東西寫回數據庫,再將新的訪問內容從數據庫暫存到緩存裏面。數據結構

2、數據結構:

最經常使用的數據結構實現方式是hash_mapDouble-Linked List,hash_map只是爲了實現鍵值key-value對應,這樣就避免了每次尋找特定值都要在雙線鏈表裏面順序查找,服務器是沒辦法負擔這種低效率的查找方法的。

咱們能夠爲鏈表節點寫一個結構體,用來定義節點的類型;而後專門寫一個類用來組織緩存信息的存放——以雙鏈表的形式。

template<class K, class T>
struct Node
{
    K key;
    T data;
    Node *next;
    Node *prev;
};
template<class K,class T>
class LRUCache
{
public:
   LRUCache(size_t size);         //typedef unsigned int size_t
   ~LRUCache();
   void Put(K key,T data);
   T Get(K key);
private:
   void Attach(Node<K,T>* node);
   void Detach(Node<K,T>* node);
private:
    hash_map<K,Node<K,T>*> hashmap;
    vector<Node<K,T>*> linkedList;
    Node<K,T>* head;
    Node<K,T>* tail;
    Node<K,T>* entries;          //temp nodes
};

看代碼太枯燥,我畫了個UML圖:

3、主要的兩個函數接口Put()和Get():

最基本的,不是存,就是取。那修改呢?合併到存裏面去了,經過鍵值key查找一個hash_map對應的value,若是value不是NULL,那麼更新value的內容便可。其實服務器緩存比較多做的是讀多寫少的東西。

由於代碼實在是太枯燥了,因此針對Put函數和Get函數畫了兩張流程圖:

其中,Detach(node)表示將這個節點從雙鏈表中解出,成爲一個獨立的節點;Attach(node)表示將node插入到頭節點head後面(表示它可能再次被用到),這樣的話,若是本身再設計一個GetFirst()函數,就能直接獲取上次的訪問結果,這種訪問連hash_map都不須要用到。

流程講解:

在上述Put函數流程圖中,注意第一個判斷「node==NULL」,這個node地址是經過hashmap映射而來的:一、若是不是NULL,說明這個節點已經存在,那麼將該節點的數據data重寫之後加到鏈表頭;
二、若是是NULL,還要進行第二個判斷「分配地址的linkedList是否是已經空了」:
2.一、若是空了,說明所有可用地址已經分配完了,那麼,將原鏈表的最後一個節點踢出鏈表(應寫入數據庫),而後將被踢出點的hashmap中對應的key-value擦除,而後再加入新節點,並在hashmap中對應好新節點的key-value;
2.二、若是不空,那麼從linkedList中分配個新地址給這個節點,同時linkedList要彈出分配完的地址,而後再將新節點加入鏈表中,對應好hashmap中的key-value。

要注意,hashmap用來對應key-value,這裏是方便查找;而vector變量linkedList也只是在初始化的時候存儲了一塊連續地址,用來分配地址,它們二者都不是用來直接構建鏈表的,鏈表是你本身創建的。

Get()函數就比較簡單了:

4、C++代碼實現:

代碼不是我原創的(後面給出原文地址),不過理清思路以後我本身實現了一遍,測試的過程實在是各類奇葩和辛苦(由於一個不注意的小地方)。

代碼實現裏用到了hash_map,注意,這個不是C++標準的一部分,因此你要本身去庫文件裏找找,通常來講庫文件都是在/usr/include下面的了,cd到這個文件夾,而後用grep找一下:

cd /usr/include

grep  -R "hash_map"

最後你會發現是這個頭文件:<ext/hash_map>,用文本文檔打開來看一下,由於是限制了命名空間的,會發現有兩個可用的命名空間,其中一個是__gnu_cxx

代碼我一開始是用Qt寫的,不事後來發現Qt無法調試(後面再說),因而最後使用Eclipse完成了調試和測試,下面先給出代碼:

  1 #include <iostream>
  2 #include<ext/hash_map>
  3 #include<vector>
  4 #include<assert.h>
  5 #include<string>
  6 #include<stdlib.h>
  7 #include<stdio.h>
  8 #include<iomanip>
  9 using namespace std;
 10 //grep -R "hash_map" /usr/include . Find the file and you will know what the namespace is.
 11 using namespace __gnu_cxx;
 12 
 13 
 14 template<class K, class T>
 15 struct Node
 16 {
 17     K key;
 18     T data;
 19     Node *next;
 20     Node *prev;
 21 };
 22 
 23 template<class K,class T>
 24 class LRUCache
 25 {
 26 public:
 27     LRUCache(size_t size)         //typedef unsigned int size_t
 28     {
 29         entries = new Node<K,T>[size];
 30         assert(entries!=NULL);
 31         for(int i = 0; i < size; i++)
 32         {
 33             linkedList.push_back(entries+i);      //Store the addr.
 34         }
 35         //Initial the double linklist
 36         head = new Node<K,T>;
 37         tail = new Node<K,T>;
 38         head->prev = NULL;
 39         head->next = tail;
 40         tail->next = NULL;
 41         tail->prev = head;
 42     }
 43     ~LRUCache()
 44     {
 45         delete head;
 46         delete tail;
 47         delete []entries;
 48     }
 49     void Put(K key,T data)
 50     {
 51         Node<K,T> *node = hashmap[key];
 52         if(node == NULL)  //node == NULL means it doesn't exist
 53         {
 54             if(linkedList.empty())  //linkedList is empty means all avaliable space have been allocated
 55             {
 56                 node = tail->prev;    //Detach the last code
 57                 Detach(node);
 58                 hashmap.erase(node->key);
 59             }
 60             else
 61             {
 62                 node = linkedList.back();  //Allocate an addr to the node
 63                 linkedList.pop_back();     //Pop the addr mentioned above
 64             }
 65             node->key = key;
 66             node->data = data;
 67             hashmap[key] = node;
 68             Attach(node);
 69         }
 70         else
 71         {
 72             Detach(node);
 73             node->data = data;
 74             Attach(node);
 75         }
 76     }
 77 
 78     T Get(K key)
 79     {
 80         Node<K,T> * node = hashmap[key];
 81         if(node)
 82         {
 83             Detach(node);
 84             Attach(node);      //Attach the node to the fisrt place
 85             return node->data;
 86         }
 87         else
 88         {
 89             return T();
 90             /*U can write some codes to test it:
 91              *void main()
 92              *{
 93              *char c = int();
 94               cout<<c<<endl;
 95               int i = char();
 96               cout<<i<<endl;
 97               cout<<char()<<endl;
 98               cout<<int()<<endl;
 99               }*/
100         }
101     }
102 private:
103     void Attach(Node<K,T>* node)
104     {
105         assert(node != NULL);
106         node->next = head->next;
107         node->next->prev = node;
108         node->prev = head;
109         head->next = node;
110     }
111     void Detach(Node<K,T>* node)
112     {
113         assert(node != NULL);
114         node->prev->next = node->next;
115         node->next->prev = node->prev;
116     }
117 
118 private:
119     hash_map<K,Node<K,T>*> hashmap;
120     vector<Node<K,T>*> linkedList;
121     Node<K,T>* head;
122     Node<K,T>* tail;
123     Node<K,T>* entries; //temp nodes
124 
125 };
126 
127 
128 int main()
129 {
130     cout << "Hello World!" << endl;
131     LRUCache<int,string> cache(10);
132     /*
133     string str = "test";
134     char buffer[10];
135     int i = 1;
136     //itoa(1,c,10);   //Not existing in the ANSI-C or C++.
137     sprintf(buffer,"%d",i);   //The alternative to the code above.
138     str.append(buffer);
139     cout<<str<<endl;
140     */
141     cache.Put(1,"test1");
142     cout<<cache.Get(1)<<endl;
143     if(cache.Get(2)=="")
144     {
145         cout<<"Node doesn't exist!"<<endl;
146     }
147     cache.Put(0,"test0");
148     cache.Put(3,"test3");
149     cache.Put(1,"test_1_again");
150     cache.Put(12,"test12");
151     cache.Put(56,"test56");
152     cout<<cache.Get(3)<<endl;
153     /*Error code. And I don't know why!!!!
154         string str="test";
155         char buffer[10];
156         for(int i = 0 ; i < 14; i++)
157         {
158             if(sprintf(buffer,"%d",i)<10)
159             {
160                 str.append(buffer);
161                 cache.Put(i,str);
162                 str="test";
163             }
164         }
165         for(int i =0; i < 14; i++)
166         {
167             if(!(i/5))
168             {
169                 cout<<cache.Get(i)<<setw(4);
170             }
171             cout<<endl;
172         }*/
173     return 0;
174 }
LRUCache

調試的時候我發現了一個問題:

坑爹了,所有節點的next指針所有都指向本身,這樣的話鏈表長得像什麼樣子呢?應該是這樣:

這個錯誤究竟是怎麼來的?

我反覆地看代碼,節點的鏈入(Attach)和取出(Detach)都是沒有問題的,並且,插入新節點的時候,已經插入過的節點爲何沒有了?Attach方法既然是正確的,那爲何節點的next爲何會指向本身?

綜合上面兩個問題,我忽然意識到:那隻能是分配地址的時候出現問題了!
因此回到構造函數分配地址的部分,我發如今for循環裏面,本應是:
linkedList.push_back(entries+i);
這樣就能順序存儲分配好的地址。
但我居然把i寫成了1,因此每一個地址都成了同一個!吐血的經歷。

最後代碼更正以後便可正確運行:

對應的測試代碼爲:

最後一筆帶過編寫過程當中遇到的三個主要問題:

一、make install 源碼包安裝的軟件怎麼卸載(最好用--prefix=""指定安裝路徑,這樣你卸載的時候直接卸載那個安裝路徑的文件夾);
二、QtCreator調試的時候爲何不顯示變量(由於你用的python版本是3.0以上的,Qt的gdb調試器不支持,本身從新裝一個2.X版本的,參考連接:http://blog.hostilefork.com/qtcreator-debugger-no-locals-ubuntu/);
三、測試代碼的時候出現一點小錯誤,搞了好久但仍是不知道爲何(在main函數中註釋的最後一段代碼,有興趣的能夠本身調試一下)。

參考文章:

一、http://www.cs.uml.edu/~jlu1/doc/codes/lruCache.html

二、http://blog.csdn.net/xiaofei_hah0000/article/details/8993617

相關文章
相關標籤/搜索