LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
常见的LRU实现策略是:
- 新数据插入到链表头部;
- 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
- 当链表满的时候,将链表尾部的数据丢弃。
但是如上实现存在一个问题:命中时需要遍历链表,找到命中的数据块索引,然后需要将数据移到头部,时间复杂度是$O(N)$。
未解决遍历带来的消耗,再此设计的是一种哈希表维护的双向链表结构,以减少寻找数据块时的时间消耗。
如上图所示,每个数据块拥有2个角度的身份,一个身份是缓存双向链表的成员,一个身份是哈希表成员。
LRU结构体设计
1 | /*定义LRU缓存的缓存单元*/ |
2 | typedef struct cacheEntryS{ |
3 | char key; /* 数据块的key */ |
4 | char data; /* 数据块的data */ |
5 | struct cacheEntryS *hashListPrev; /* 缓存哈希表指针,指向哈希链表的前一个元素 */ |
6 | struct cacheEntryS *hashListNext; /* 缓存哈希表指针,指向哈希链表的后一个元素 */ |
7 | struct cacheEntryS *lruListPrev; /* 缓存双向链表指针,指向链表的前一个元素 */ |
8 | struct cacheEntryS *lruListNext; /* 缓存双向链表指针,指向链表的后一个元素 */ |
9 | }cacheEntryS; |
10 | /* 定义LRU缓存*/ |
11 | typedef struct LRUCacheS{ |
12 | int cacheCapacity; /*缓存的容量*/ |
13 | cacheEntryS **hashMap; /*缓存的哈希表*/ |
14 | cacheEntryS *lruListHead; /*缓存双向链表表头*/ |
15 | cacheEntryS *lruListTail; /*缓存双向链表表尾*/ |
16 | int lruListSize; /*缓存双向链表节点个数*/ |
17 | }LRUCacheS; |
双休链表相关接口实现
1 | /*从双向链表中删除指定节点*/ |
2 | static void removeFromList(LRUCacheS *cache, cacheEntryS *entry) |
3 | { |
4 | /*链表为空*/ |
5 | if (cache->lruListSize==0) |
6 | return ; |
7 | if (entry==cache->lruListHead && entry==cache->lruListTail) { |
8 | /*链表中仅剩当前一个节点*/ |
9 | cache->lruListHead = cache->lruListTail = NULL; |
10 | } else if (entry == cache->lruListHead) { |
11 | /*欲删除节点位于表头*/ |
12 | cache->lruListHead = entry->lruListNext; |
13 | cache->lruListHead->lruListPrev = NULL; |
14 | } else if (entry == cache->lruListTail) { |
15 | /*欲删除节点位于表尾*/ |
16 | cache->lruListTail = entry->lruListPrev; |
17 | cache->lruListTail->lruListNext = NULL; |
18 | } else { |
19 | /*其他非表头表尾情况,直接摘抄节点*/ |
20 | entry->lruListPrev->lruListNext = entry->lruListNext; |
21 | entry->lruListNext->lruListPrev = entry->lruListPrev; |
22 | } |
23 | /*删除成功,链表节点数减1*/ |
24 | cache->lruListSize--; |
25 | } |
26 | |
27 | /* 将节点插入到链表表头 */ |
28 | static cacheEntryS * insertToListHead(LRUCacheS *cache, cacheEntryS *entry) |
29 | { |
30 | cacheEntryS *removedEntry = NULL; |
31 | if (++cache->lruListSize > cache->cacheCapacity) { |
32 | /* 如果缓存满了,即链表当前节点数已等于缓存容量,那么需要先删除链表表尾节点,即淘汰最久没有被访问到的缓存数据单元*/ |
33 | removedEntry = cache->lruListTail; |
34 | removeFromList(cache, cache->lruListTail); |
35 | } |
36 | if (cache->lruListHead==NULL&&cache->lruListTail==NULL) { |
37 | /*如果当前链表为空链表*/ |
38 | cache->lruListHead = cache->lruListTail = entry; |
39 | } else { |
40 | /*当前链表非空,插入表头*/ |
41 | entry->lruListNext = cache->lruListHead; |
42 | entry->lruListPrev = NULL; |
43 | cache->lruListHead->lruListPrev = entry; |
44 | cache->lruListHead = entry; |
45 | } |
46 | return removedEntry; |
47 | } |
48 | |
49 | /*辅助性接口,用于保证最近访问的节点总是位于链表表头*/ |
50 | static void updateLRUList(LRUCacheS *cache, cacheEntryS *entry) |
51 | { |
52 | /*将节点从链表中摘抄*/ |
53 | removeFromList(cache, entry); |
54 | /*将节点插入链表表头*/ |
55 | insertToListHead(cache, entry); |
56 | } |
哈希表相关接口实现
1 | /*哈希函数*/ |
2 | static int hashKey(LRUCacheS *cache, char key) |
3 | { |
4 | return (int)key%cache->cacheCapacity; |
5 | } |
6 | /*从哈希表获取缓存单元*/ |
7 | static cacheEntryS *getValueFromHashMap(LRUCacheS *cache, int key) { |
8 | /*1.使用函数函数定位数据存放在哪个槽*/ |
9 | cacheEntryS *entry = cache->hashMap[hashKey(cache,key)]; |
10 | /*2.遍历查询槽内链表,找到准确的数据项*/ |
11 | while (entry) { |
12 | if (entry->key == key) |
13 | break; |
14 | entry = entry->hashListNext; |
15 | } |
16 | return entry; |
17 | } |
18 | /*向哈希表插入缓存单元*/ |
19 | static void insertentryToHashMap(LRUCacheS *cache, cacheEntryS *entry) |
20 | { |
21 | /*1.使用函数函数定位数据存放在哪个槽*/ |
22 | cacheEntryS *n = cache->hashMap[hashKey(cache, entry->key)]; |
23 | if (n!=NULL) { |
24 | /*2.如果槽内已有其他数据项,将槽内数据项与当前欲加入数据项链成链表,当前欲加入数据项为表头*/ |
25 | entry->hashListNext = n; |
26 | n->hashListPrev = entry; |
27 | } |
28 | /*3.将数据项放入加入哈希槽内*/ |
29 | cache->hashMap[hashKey(cache,entry->key)] = entry; |
30 | } |
31 | /*从哈希表删除缓存单元*/ |
32 | static void removeEntryFromHashMap(LRUCacheS *cache, cacheEntryS *entry) |
33 | { |
34 | /*无需做任何删除操作的情况*/ |
35 | if (NULL==entry || NULL==cache || NULL==cache->hashMap) return ; |
36 | /*1.使用函数函数定位数据存放在哪个槽*/ |
37 | cacheEntryS *n = cache->hashMap[hashKey(cache,entry->key)]; |
38 | /*2.遍历槽内链表,找到欲删除节点,将节点从哈希表摘除*/ |
39 | while (n) { |
40 | if (n->key==entry->key) {/*找到欲删除节点,将节点从哈希表摘抄*/ |
41 | if (n->hashListPrev) { |
42 | n->hashListPrev->hashListNext = n->hashListNext; |
43 | } else { |
44 | cache->hashMap[hashKey(cache, entry->key)] = n->hashListNext; |
45 | } |
46 | if (n->hashListNext) |
47 | n->hashListNext->hashListPrev = n->hashListPrev; |
48 | return ; |
49 | } |
50 | n = n->hashListNext; |
51 | } |
52 | } |
53 | ` |
LRU相关接口实现
1 | /*将数据放入LRU缓存中*/ |
2 | int LRUCacheSet(void *lruCache, char key, char data) |
3 | { |
4 | LRUCacheS *cache = (LRUCacheS *)lruCache; |
5 | /*从哈希表查找数据是否已经在缓存中*/ |
6 | cacheEntryS *entry = getValueFromHashMap(cache, key); |
7 | if (entry!=NULL) {/*数据已经在缓存中*/ |
8 | /*更新数据,将该数据项更新至链表表头*/ |
9 | entry->data = data; |
10 | updateLRUList(cache, entry); |
11 | } else { |
12 | /*数据没在缓存中*/ |
13 | /*新建缓存单元*/ |
14 | entry = newCacheEntry(key, data); |
15 | /*将新建缓存单元插入链表表头*/ |
16 | cacheEntryS *removedEntry = insertToListHead(cache, entry); |
17 | if (NULL != removedEntry) { |
18 | /*将新建缓存单元插入链表表头过程中,发生缓存满了的情况,需要淘汰最久没有被访问到的缓存数据单元*/ |
19 | removeEntryFromHashMap(cache, removedEntry); |
20 | freeCacheEntry(removedEntry); |
21 | } |
22 | /*将新建缓存单元插入哈希表*/ |
23 | insertentryToHashMap(cache, entry); |
24 | } |
25 | return 0; |
26 | } |
27 | /*从缓存中获取数据*/ |
28 | char LRUCacheGet(void *lruCache, char key) |
29 | { |
30 | LRUCacheS *cache = (LRUCacheS *)lruCache; |
31 | /*从哈希表查找数据是否已经在缓存中*/ |
32 | cacheEntryS *entry = getValueFromHashMap(cache,key); |
33 | if (NULL != entry) { |
34 | /*缓存中存在该数据,更新至链表表头*/ |
35 | updateLRUList(cache, entry); |
36 | /*返回数据*/ |
37 | return entry->data; |
38 | } else { |
39 | /*缓存中不存在相关数据*/ |
40 | return '\0'; |
41 | } |
42 | } |