LRU
最近最少使用置换算法。
有一个容量capacity
,需要支持插入一个(key, value)
,按照key
查找value
。
插入:
- 不在cache中:
- 还有容量,直接插入到末尾
- 没有容量,就挤掉开头最旧的,再插入到末尾
- 在cache中:
查找:
核心:用双向链表按序维护所有value
,以支持快速删除操作;用哈希表维护所有key
到链表节点的映射,用于快速查找操作。
注意list
中必须key和value都存,因为替换的时候,得把替换掉的元素从unordered_map
中移除。
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
| class LRUCache { public: LRUCache(int capacity) : cap(capacity) {}
int get(int key) { auto it = pos.find(key); if (it != pos.end()) { int value = it->second->second; lst.erase(it->second); it->second = lst.emplace(lst.end(), key, value); return value; } else { return -1; } }
void put(int key, int value) { auto it = pos.find(key); if (it != pos.end()) { lst.erase(it->second); it->second = lst.emplace(lst.end(), key, value); } else if (lst.size() < cap) { pos.emplace(key, lst.emplace(lst.end(), key, value)); } else { pos.erase(lst.begin()->first); lst.pop_front(); pos.emplace(key, lst.emplace(lst.end(), key, value)); } }
private: unordered_map<int, list<pair<int, int>>::iterator> pos; list<pair<int, int>> lst; int cap; };
|
优化
处理预读问题:在读取时,由于局部性原理,会预读连续的若干页,但这些页可能并不会真正需要读取。
解决方案:构造两级链表。预读先放到一级,真正读取时再从一级移到二级;二级中被淘汰的再放回一级。
处理偶发的批量读问题:有时会连续读取若干页,但读取这一次后就不再使用,此时会污染整个二级链表。
解决方案:提高从一级链表移到二级链表的门槛,比如第二次读才能移过去。
扩展
增加过期时间。
线程安全。
LFU
最不常使用替换算法。需要维护被访问次数,如果有多个访问次数最少的,则退化为LRU,选择最近访问时间最老的。