LFU和LRU是两种典型的缓存页面置换算法,了解其底层以及运行机制是CSer的必修课。
缓存是计算机中广泛应用的一种技术,包括CPU L1/L2/L3 cache,RAM中的cache,网络协议tcp缓冲区,OS页面置换算法,redis缓存等等。需要形成对“缓存”的一个整体认识。
LRU(Least Recently Used): 最近最久未使用算法,即最久不使用的元素最先淘汰。
LFU(Least Frequently Used):最近最不频繁使用算法,即考虑访问次数,如果访问次数相同,考虑时间先后。
实际上,这两者都考虑的是程序的局部性原理。
LRU:adopts 'bidirectional list' and 'hash table' technology
LFU:adopts double hash table and bidirectional list,
can also use hash table and rbtree(set), but the time complexity is O(logn).
1 /** 2 * author: ray' 3 * date: 2021/07/15 4 * contact: 2582171339@qq.com 6 * github: https://github.com/XuanRay 7 * 8 * description: LRU和LFU的实现 9 * LRU code copyed from "小林coding" 10 * LFU is coded by ray' 11 */ 12 13 #include <iostream> 14 #include <list> 15 #include <unordered_map> 16 #include <string> 17 18 // LRU:adopts 'bidirectional list' and 'hash table' technology 19 template <typename KeyT, typename ValueT> 20 class LRUCache { 21 public: 22 typedef std::pair<KeyT, ValueT> Pair; // using Pair = std::pair<KeyT, ValueT>; 23 typedef std::list<Pair> List; 24 typedef std::unordered_map<KeyT, typename List::iterator> uMap; 25 26 explicit LRUCache(int cap) : m_capacity(cap) {} 27 28 bool put(KeyT key, ValueT value); //add element to LRUCache 29 bool get(KeyT key, ValueT* pValue); //visit element in LRUCache 30 31 std::size_t size() const { 32 return m_capacity; 33 } 34 35 LRUCache(const LRUCache&) = delete; 36 LRUCache& operator=(const LRUCache&) = delete; 37 38 ~LRUCache() { 39 m_map.clear(); 40 m_list.clear(); 41 } 42 43 private: 44 std::size_t m_capacity; // list capacity 45 List m_list; // bidirectional list 46 uMap m_map; // hash table 47 }; 48 49 50 template <typename KeyT, typename ValueT> 51 bool LRUCache<KeyT, ValueT>::put(KeyT key, ValueT value) { 52 // find key from hash table 53 typename uMap::iterator iter = m_map.find(key); 54 55 // if key is existed 56 if (iter != m_map.end()) { 57 // delete from bi-list and hash table 58 m_list.erase(iter->second); 59 m_map.erase(iter); 60 } 61 62 // insert the element into bi-list front and hash table 63 m_list.push_front(std::make_pair(key, value)); 64 m_map[key] = m_list.begin(); 65 66 // judge whether exceed capacity 67 if (m_list.size() > m_capacity) { 68 KeyT endKey = m_list.back().first; // unlike member list::end, which returns an iterator just past this element 69 // list::back returns a direct reference 70 m_list.pop_back(); 71 m_map.erase(endKey); 72 } 73 74 return true; 75 } 76 77 template <typename KeyT, typename ValueT> 78 bool LRUCache<KeyT, ValueT>::get(KeyT key, ValueT* pvalue) { 79 // find whether key is in hash table 80 typename uMap::iterator mapIter = m_map.find(key); 81 if (mapIter == m_map.end()) { 82 return false; 83 } 84 85 // get bi-list node 86 typename List::iterator listIter = mapIter->second; 87 88 ValueT value = listIter->second; 89 90 // delete node from bi-list and insert into the front. 91 m_list.erase(listIter); //这个迭代器已经失效了,还能push_front? 迭代器失效问题 92 m_list.push_front(std::make_pair(key, value)); 93 94 // hash table changed 95 m_map[key] = m_list.begin(); 96 97 // save the value 98 *pvalue = value; 99 100 return true; 101 } 102 103 104 105 // FLU adopted double hash table and bidirectional list 106 107 // Node information 108 template <typename KeyT, typename ValueT> 109 struct Node { 110 KeyT key; 111 ValueT value; 112 int freq; 113 114 Node(KeyT k, ValueT v, int f) : key(k), value(v), freq(f) {} 115 }; 116 117 // LFUCache 118 template<typename KeyT, typename ValueT> 119 class LFUCache { 120 public: 121 typedef std::unordered_map<int, std::list<Node<KeyT, ValueT>>> Freq_Table; 122 typedef std::unordered_map<KeyT, typename std::list<Node<KeyT, ValueT>>::iterator> Key_Table; 123 124 // ctor 125 LFUCache(int cap = 1) : m_minfreq(0), m_capacity(cap) {} 126 127 // put and get 128 bool get(KeyT key, ValueT* pValue); 129 bool put(KeyT key, ValueT value); 130 131 std::size_t get_MinFreq() const { 132 return this->m_minfreq; 133 } 134 135 std::size_t get_Capacity() const { 136 return this->m_capacity; 137 } 138 139 // dtor 140 ~LFUCache() { 141 m_freqTable.clear(); 142 m_keyTable.clear(); 143 } 144 145 private: 146 std::size_t m_minfreq; 147 std::size_t m_capacity; 148 Freq_Table m_freqTable; 149 Key_Table m_keyTable; 150 }; 151 152 template<typename KeyT, typename ValueT> 153 bool LFUCache<KeyT, ValueT>::get(KeyT key, ValueT* pValue) { 154 // find whether key is in key hash table 155 auto iter = m_keyTable.find(key); 156 if (iter == m_keyTable.end()) { 157 return false; 158 } 159 160 typename std::list<Node<KeyT, ValueT>>::iterator node = iter->second; 161 *pValue = node->value; 162 int freq = node->freq; 163 164 // delete this node 165 m_freqTable[freq].erase(node); 166 167 // if current list is NULL, delete it from freq hash table 168 if (m_freqTable[freq].size() == 0) { 169 m_freqTable.erase(freq); 170 if (m_minfreq == freq) { 171 m_minfreq += 1; 172 } 173 } 174 175 // insert the node to freq + 1 list 176 m_freqTable[freq + 1].push_front(Node<KeyT, ValueT>(key, *pValue, freq + 1)); 177 178 //m_freqTable[freq + 1].push_front(new Node<KeyT, ValueT>(key, val, freq + 1)); 179 m_keyTable[key] = m_freqTable[freq + 1].begin(); 180 181 return true; 182 } 183 184 template<typename KeyT, typename ValueT> 185 bool LFUCache<KeyT, ValueT>::put(KeyT key, ValueT value) { 186 typename Key_Table::iterator iter = this->m_keyTable.find(key); 187 188 // if key is not found in key hash table 189 if (iter == m_keyTable.end()) { 190 // if full 191 if (m_capacity == m_keyTable.size()) { 192 auto it = m_freqTable[m_minfreq].back(); // list的back()返回的是reference 193 m_keyTable.erase(it.key); 194 m_freqTable[m_minfreq].pop_back(); 195 if (m_freqTable[m_minfreq].size() == 0) { 196 m_freqTable.erase(m_minfreq); 197 } 198 } 199 m_freqTable[1].push_front(Node<KeyT, ValueT>(key, value, 1)); 200 m_keyTable[key] = m_freqTable[1].begin(); 201 m_minfreq = 1; 202 } 203 else { // otherwise 204 auto node = iter->second; 205 int freq = node->freq; 206 m_freqTable[freq].erase(node); 207 if (m_freqTable[freq].size() == 0) { 208 m_freqTable.erase(freq); 209 if (m_minfreq == freq) { 210 m_minfreq += 1; 211 } 212 } 213 m_freqTable[freq + 1].push_front(Node<KeyT, ValueT>(key, value, freq + 1)); 214 m_keyTable[key] = m_freqTable[freq + 1].begin(); 215 } 216 217 return true; 218 } 219 220 221 222 // test LRU 223 void testLRU() { 224 LRUCache<int, std::string> lruCache(3); 225 226 lruCache.put(1, "r"); 227 lruCache.put(2, "a"); 228 lruCache.put(3, "y"); 229 230 std::string value; 231 bool ret = true; 232 233 ret = lruCache.get(1, &value); 234 std::cout << "value = " << value << ", ret = " << ret << "\n"; 235 236 lruCache.put(4, "'"); 237 value = ""; 238 ret = lruCache.get(2, &value); 239 std::cout << "value = " << value << ", ret = " << ret << "\n"; 240 } 241 242 void testLFU() { 243 LFUCache<int, std::string> lfuCache(3); 244 lfuCache.put(1, "r"); 245 lfuCache.put(2, "a"); 246 lfuCache.put(3, "y"); 247 248 std::string value; 249 bool ret = true; 250 251 ret = lfuCache.get(1, &value); 252 std::cout << "value = " << value << ", ret = " << ret << "\n"; 253 254 value = ""; 255 ret = lfuCache.get(2, &value); 256 std::cout << "value = " << value << ", ret = " << ret << "\n"; 257 258 lfuCache.put(4, "s"); 259 260 value = ""; 261 ret = lfuCache.get(3, &value); 262 std::cout << "value = " << value << ", ret = " << ret << "\n"; 263 } 264 265 266 // test LRU and LFU 267 int main() { 268 testLRU(); 269 270 std::cout << "\n\n"; 271 std::cout << "----------------------------------------------\n\n"; 272 273 testLFU(); 274 275 system("pause"); 276 return 0; 277 }