概念 常用的缓存过期淘汰策略有:LRU、LFU、FIFO等,因为FIFO(First in first out)算法是一种先进先出的队列,算法很简单,就不具体描述了,下面介绍LRU和LFU的算法策略。 LRU:最近最少使用淘汰算法(Least Recently Used)。如果一个数据在最近一段时间内没有被访问到,那么可以认为在将来它被访问的可能性也很小。因此当空间满时,最久没有被访问的数据最先被淘汰。 LFU:最不经常使用淘汰算法(Least Frequently Used),如果一个数据在最近一段时间内很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此当空间满时,最小频率访问的数据最先被淘汰。
算法实现 LRU: 最常见的算法是使用一个链表保存缓存数据,不考虑时间复杂度的基本算法思想如下:
新数据插入到链表头。
缓存命中的数据(被访问到的),将移到链表头。
当链表满时,将链表尾部数据淘汰(丢弃)。
leetcode上关于LRU算法的题目:https://leetcode-cn.com/problems/lru-cache/
O(1)时间复杂度的解法: 【哈希表+双向链表】算法思想:
定义一个哈希表ket_table,该哈希表的索引为key,每个索引存放链表里的内存地址,链表中存放着缓存数据。
当执行get(key)操作时,如果该key存在于哈希表中,通过key可以直接得到缓存的内存地址,将值取出返回,如果不存在则返回-1。当查找到缓存时,需要将缓存数据置于链表头部,表示该缓存是最新被访问的。
当执行put(key, value)操作时,如果key存在于哈希表中,删除链表中的数据,再将新数据插入到链表头部,并将头部的内存地址赋值给该key的哈希表中,如果不存在,则判断是否缓存已满,没有满则直接插入链表和哈希表中,满了则删除链表尾部的缓存数据,然后再进行插入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class LRUCache { int m_capacity; list <pair <int , int > > lrus; unordered_map <int , list <pair <int , int > >::iterator> m_key_table; public : LRUCache(int capacity) { m_capacity = capacity; m_key_table.clear(); } int get (int key) { if (m_capacity == 0 ) return -1 ; if (m_key_table.count(key)) { int value = m_key_table[key]->second; lrus.erase(m_key_table[key]); lrus.push_front({key, value}); m_key_table[key] = lrus.begin(); return value; } return -1 ; } void put (int key, int value) { if (m_capacity == 0 ) return ; if (m_key_table.count(key)) { lrus.erase(m_key_table[key]); } else if (m_key_table.size() == m_capacity) { m_key_table.erase(lrus.back().first); lrus.pop_back(); } lrus.push_front({key, value}); m_key_table[key] = lrus.begin(); } };
LFU: leetcode上关于LFU算法的题目:https://leetcode-cn.com/problems/lfu-cache/ O(1)时间复杂度解法: 【哈希表+双向链表】算法思想:
该算法与LRU算法类似,不过LFU需要用到访问频率信息,所以该算法需要两个哈希表实现。
第一个哈希表freq_table的索引为freq(访问频率),每个索引存放一个双向链表,第二个哈希表key_table的索引为key,每个索引存放该key在链表中的内存地址。还需要一个类变量m_min_freq用于保存最小的访问频率,当需要进行删除缓存的时候,可以用该变量定位缓存的位置进行删除。
当执行get(key)操作时,通过key在key_table中获取缓存的内存地址,如果不存在,则返回-1,如果存在,则取出缓存数据,访问频率需要增加1,所以要将缓存放入freq_table[freq+1]的链表头部。
当执行put(key, value)操作时,判断该key是否在key_table中,如果不存在,并且容量没有满,则直接插入到freq_table[1]的链表中,如果容量满了,则通过m_min_freq找到最小访问频率的链表,从尾部删除数据,然后将新数据直接插入到freq_table[1]的链表头部。如果key存在与key_table中,则取出链表中的缓存数据,更新新的value,并增加访问频率(freq+1),将插入到freq_table[1]的链表头部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 typedef struct LFUStruct { int key, value, freq; LFUStruct (int k, int v, int f) : key(k), value(v), freq(f) {} } lfu; class LFUCache { int m_capacity; int m_min_freq; unordered_map <int , list <lfu>::iterator> m_key_table; unordered_map <int , list <lfu>> m_freq_table; public : LFUCache(int capacity) { m_capacity = capacity; m_min_freq = 0 ; m_key_table.clear(); m_freq_table.clear(); } int get (int key) { if (m_capacity == 0 ) return -1 ; auto it = m_key_table.find(key); if (it == m_key_table.end()) return -1 ; auto get_it = it->second; int value = get_it->value; int freq = get_it->freq; m_freq_table[freq].erase(get_it); if (m_freq_table[freq].size() == 0 ) { m_freq_table.erase(freq); if (m_min_freq == freq) m_min_freq += 1 ; } m_freq_table[freq + 1 ].push_front(lfu(key, value, freq + 1 )); m_key_table[key] = m_freq_table[freq + 1 ].begin(); return value; } void put (int key, int value) { if (m_capacity == 0 ) return ; auto it = m_key_table.find(key); if (it == m_key_table.end()) { if (m_key_table.size() >= m_capacity) { auto del = m_freq_table[m_min_freq].back(); m_freq_table[m_min_freq].pop_back(); m_key_table.erase(del.key); if (m_freq_table[m_min_freq].size() == 0 ) { m_freq_table.erase(m_min_freq); } } m_freq_table[1 ].push_front(lfu(key, value, 1 )); m_key_table[key] = m_freq_table[1 ].begin(); m_min_freq = 1 ; } else { auto update_it = it->second; int freq = update_it->freq; m_freq_table[freq].erase(update_it); if (m_freq_table[freq].size() == 0 ) { m_freq_table.erase(freq); if (m_min_freq == freq) { m_min_freq += 1 ; } } m_freq_table[freq + 1 ].push_front(lfu(key, value, freq + 1 )); m_key_table[key] = m_freq_table[freq + 1 ].begin(); } } };