目錄:html
1、介紹;node
2、數據結構;python
後臺開發必備知識,不過我不是搞這個的,只是由於好久之前就想寫這些東西,事情多,拖到如今。寫的過程裏面發現不少問題,不會所有說,最後會順帶提一提。數據庫
注意,本篇筆記只是對接口寫法作了記錄,並無進行更嚴格的設計和限制,包括更嚴密的封裝,這裏只是學習它實現的原理。ubuntu
不過有些idea仍是要知道的,系統定時對緩存進行清除並加入知足條件的新數據,是根據:訪問時間,訪問次數,可用緩存容量(分配到的內存)等因素決定的,實際設計其實不少東西須要考慮。緩存
LRU,Least Recently Used,最近最少使用,服務器緩存經常使用算法的一種。服務器
好比說一些系統登陸的操做,不可能每次你訪問系統都去調用數據庫的東西,若是能劃出一些空間來,好比說500M,用來緩存這些東西,這樣用戶訪問的時候先在緩存裏找,找不到,再去訪問數據庫,同時把被訪問的內容放到緩存裏面(咱們能夠假設這些東西還會常常被訪問)。然而,咱們分配用來作緩存(Cache)的空間確定是有限的,總不可能從數據庫讀的東西所有放到緩存裏,因此,當緩存裏的內容達到上限值的時候,咱們就要把最少使用的東西寫回數據庫,再將新的訪問內容從數據庫暫存到緩存裏面。數據結構
最經常使用的數據結構實現方式是hash_map和Double-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圖:
最基本的,不是存,就是取。那修改呢?合併到存裏面去了,經過鍵值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()函數就比較簡單了:
代碼不是我原創的(後面給出原文地址),不過理清思路以後我本身實現了一遍,測試的過程實在是各類奇葩和辛苦(由於一個不注意的小地方)。
代碼實現裏用到了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 }
調試的時候我發現了一個問題:
坑爹了,所有節點的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