LRU Cache
Problem:
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before.
分析:
先用例子回顾LRU调度算法,假设内存中页面容量是3,而共有6页,编号依次为1至6。现在有十次页面调用,依次为1, 3, 1, 2, 4, 5, 3, 4, 6, 1。首先假设内存中空,内存中页面的替换顺序为
(1) 1
(2) 3, 1
(3) 1, 3
(4) 2, 1, 3
(5) 4, 2, 1
(6) 5, 4, 2
(7) 3, 5, 4
(8) 4, 3, 5
(9) 6, 4, 3
(10) 1, 6, 4
十次页面调用中,除去第(3)(8)两次外,均发生了缺页中断。观察以上的序列调用情况,整体上页面被组织成了一个队列形式,刚刚使用的页面放在队尾,而每次被替换出去的页面都放在队头。如果没有缺页中断,页面在内存中,也要将刚刚访问的页面放在队尾。在内存容量没有到达前,所有的缺页中断均将页面直接接在队尾。
以上的分析能够看出,页面的替换算法中涉及了很多的元素顺序调整,也就是队列中的移除与插入操作,所以使用链表可以降低时间开销。然而,检测访问页面是否在内存中是较耗时的操作,为了加速访问效率,可以用map加速查找。
双向队列可以方便删除与插入操作,使用哨兵能够让代码变得简洁,所以综上,采用带哨兵的双向链表与map结合,能够达到较好的代码编写难度与效率的平衡。map使用了红黑树的结构,可以保证get与set操作的平均时间复杂度在O(log n)。
1 struct CacheBlock { 2 int key; 3 int value; 4 CacheBlock *prev; 5 CacheBlock *next; 6 7 CacheBlock(int k, int v) : key(k), value(v), prev(NULL), next(NULL) {} 8 }; 9 10 class LRUCache{ 11 CacheBlock *list; // doubly list with sentinel 12 int capacity; 13 int size; 14 map<int, CacheBlock*> caches; 15 16 public: 17 LRUCache(int cap) : list(NULL), capacity(cap), size(0) { 18 // initialize sentinel 19 list = new CacheBlock(-1, -1); 20 list->prev = list->next = list; 21 } 22 23 ~LRUCache() { 24 if(list) { 25 delete list; 26 list = NULL; 27 } 28 } 29 30 int get(int key) { 31 if(caches.find(key) == caches.end()) 32 return -1; 33 else 34 return caches[key]->value; 35 } 36 37 void set(int key, int value) { 38 if(caches.find(key) == caches.end()) { // page fault 39 if(size < capacity) { 40 // add head 41 CacheBlock *cb = new CacheBlock(key, value); 42 insert(cb); 43 caches[key] = cb; 44 ++size; 45 } else { 46 // remove tail 47 CacheBlock *tail = list->prev; 48 remove(tail); 49 caches.erase(tail->key); 50 // add head 51 tail->key = key; 52 tail->value = value; 53 tail->next = tail->prev = NULL; 54 insert(tail); 55 caches[key] = tail; 56 } 57 } else { 58 // find page 59 CacheBlock *cb = caches[key]; 60 // remove page 61 remove(cb); 62 // add head 63 cb->value = value; 64 cb->next = cb->prev = NULL; 65 insert(cb); 66 } 67 } 68 69 void insert(CacheBlock *cb) { 70 // insert to front of the list 71 cb->next = list->next; 72 list->next->prev = cb; 73 list->next = cb; 74 cb->prev = list; 75 } 76 77 void remove(CacheBlock *cb) { 78 cb->prev->next = cb->next; 79 cb->next->prev = cb->prev; 80 } 81 };