双向LRU链表

本文介绍了一种使用双向链表和哈希表实现的LRU(最近最少使用)缓存淘汰算法,通过具体C语言代码展示了如何高效地进行缓存管理和查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LRU是最近最少使用算法。一般内存管理的时候采用LRU算法可以提高性能。

将cache缓存块位置用LRU双向链表链接起来,将新加入的块直接放到链表的头,当一个块被命中后,把该块调整到链表的头,这样经过多次操作之后,最近被命中过的块就会向链表头部移动,而没有被命中的内容会向链表尾部移动,需要替换时,就直接从链表尾部替换即可。新的内容直接插入链表头部,这样就实现了LRU的思想。

算法中维护了一个freelist,用于管理空闲块的位置,每次需要插入新的内容时,就从freelist里面获取一个空闲块,将新内容复制,然后插入到链表头部;删除时就从链表尾部删除结点,然后更新到freelist里;查找一个结点时,在一般链表中的时间复杂度是O(n),为了提高查找效率,在算法中加入了一个hash表,每次新加入的结点都会更新到hash表里,删除结点时会从hash表中移除,这样查找的时候直接从hash表里查询,获取结点的位置,从而时间复杂度为O(1)。

C语言实现的代码如下(已经测试通过了):

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

#define LRU_SIZE 20
#define HASH_BASE 20
#define LRU_SUCCESS 0
#define LRU_ERROR -1
#define LRU_MISS 0
#define LRU_HIT 1
#define LRU_HASH_KEY(_key,_base) (int)(((int)_key)%(_base))

typedef struct _LRU_NODE{
 int id;
 int key;
 int free;
 int prev;
 int next;
}LRU_NODE,*PLRU_NODE;
typedef struct _LRU_HASH{
 unsigned long basesize;
 int base[HASH_BASE];
}LRU_HASH,*PLRU_HASH;

typedef struct _LRU_LIST{
 int capacity;
 int usage;
 LRU_NODE bucket[LRU_SIZE];
 int freelist;
 int head;
 int tail;
 LRU_HASH hash;
}LRU_LIST,*PLRU_LIST;

int lru_init(PLRU_LIST list, int capacity, int hashbase)
{
 int i;
 PLRU_NODE node;
 list->capacity = capacity;
 list->usage = 0;
 list->head = -1;
 list->tail = -1;
 list->freelist = -1;
 for(i = capacity - 1; i >= 0; i--)
 {
 list->bucket[i].id = i;
 list->bucket[i].key = -1;
 list->bucket[i].free = 1;
 list->bucket[i].prev = -1;
 list->bucket[i].next = -1;
 if(list->freelist == -1)
 {
 list->freelist = list->bucket[i].id;
 }
 else
 {
 node = &(list->bucket[list->freelist]);
 node->prev = list->bucket[i].id;
 list->bucket[i].next = node->id;
 list->freelist = list->bucket[i].id;
 }
 }
 for(i = 0; i < hashbase; i++)
 {
 list->hash.base[i] = -1;
 }
 list->hash.basesize = hashbase;
 return LRU_SUCCESS;
}
int lru_sethead(PLRU_LIST list, PLRU_NODE node)
{
 PLRU_NODE tempnode;
 if(node->free)
 return LRU_SUCCESS;
 if(node->id == list->head)
 return LRU_SUCCESS;
 if(list->head == -1)
 {
 //应该不会到这里面来 
 list->head = node->id;
 list->tail = node->id;
 node->prev = -1;
 node->next = -1;
 }
 else
 {
 if(list->tail == node->id)
 {
 list->tail = node->prev;
 tempnode = &(list->bucket[list->tail]);
 tempnode->next = -1;
 
 }
 else 
 {
 tempnode = &(list->bucket[node->prev]);
 tempnode->next = node->next;
 tempnode = &(list->bucket[node->next]);
 tempnode->prev = node->prev;
 }
 tempnode = &(list->bucket[list->head]);
 tempnode->prev = node->id;
 node->next = list->head;
 node->prev = -1;
 list->head = node->id;
 }
 return LRU_SUCCESS;
}
int lru_getfree(PLRU_LIST list, PLRU_NODE *freenode)
{
 PLRU_NODE node = NULL;
 if(list->freelist != -1)
 {
 node = &(list->bucket[list->freelist]);
 list->freelist = node->next;
 if(list->freelist != -1)
 {
 list->bucket[list->freelist].prev = -1;
 }
 node->free = 1;
 *freenode = node;
 return LRU_SUCCESS;
 }
 else
 {
 *freenode = NULL;
 return LRU_ERROR;
 }
}
/*lru_delete:delete tail lru node
list:lru list
*/
int lru_delete(PLRU_LIST list)
{
 PLRU_NODE tempnode;
 int hashkey;
 if(list->tail == -1)
 return LRU_ERROR;
 tempnode = &(list->bucket[list->tail]);
 list->tail = tempnode->prev;
 if(list->tail != -1)
 list->bucket[list->tail].next = -1;
 else
 list->head = -1;

 //删除hash结点
 hashkey = LRU_HASH_KEY(tempnode->key, list->hash.basesize);
 list->hash.base[hashkey] = -1;
 
 if(list->freelist != -1)
 {
 tempnode->next = list->freelist;
 list->bucket[list->freelist].prev = tempnode->id;
 list->freelist = tempnode->id;
 tempnode->prev = -1;
 tempnode->free = 1;
 tempnode->key = -1;
 }
 else
 {
 list->freelist = tempnode->id;
 tempnode->key = -1;
 tempnode->free = 1;
 tempnode->prev = -1;
 tempnode->next = -1;
 }
 list->usage--;
 return LRU_SUCCESS;
}
int lru_insert(PLRU_LIST list, PLRU_NODE node)
{
 //对于list满的情况和插入有相同key的node
 //在函数调用之前作判断
 PLRU_NODE tempnode;
 int key;
 int hashkey;
 //插入lru hash表
 key = node->key;
 hashkey = LRU_HASH_KEY(key, list->hash.basesize);
 list->hash.base[hashkey] = node->id;
 
 if(list->head == -1)
 {
 list->head = node->id;
 list->tail = node->id;
 node->prev = -1;
 node->next = -1;
 }
 else
 {
 tempnode = &(list->bucket[list->head]);
 node->next = list->head;
 tempnode->prev = node->id;
 list->head = node->id;
 node->prev = -1;
 }
 node->free = 0;
 list->usage++;
 return LRU_SUCCESS;
}
int lru_inquiry(PLRU_LIST list, int key)
{
 PLRU_NODE tempnode;
 int hashkey;
 if(list->head == -1)
 return LRU_MISS;

 hashkey = LRU_HASH_KEY(key, list->hash.basesize);
 if(list->hash.base[hashkey] == -1)
 return LRU_MISS;
 else
 {
 tempnode = &(list->bucket[list->hash.base[hashkey]]);
 }
 //没加lru hash表之前的查询方式
 /*tempnode = &(list->bucket[list->head]);
 while(tempnode && tempnode->key != key)
 {
 if(tempnode->next == -1)
 tempnode = NULL;
 else
 tempnode = &(list->bucket[tempnode->next]);
 }
 if(!tempnode)
 return LRU_MISS;*/
 lru_sethead(list,tempnode);
 return LRU_HIT;
}
int lru_show(PLRU_LIST list)
{
 PLRU_NODE node;
 if(list->head == -1)
 {
 printf("list is empty!\n");
 return LRU_ERROR;
 }
 node = &(list->bucket[list->head]);
 while(node)
 {
 printf("id:%d,key:%d\n", node->id, node->key);
 if(node->next == -1)
 node = NULL;
 else
 node = &(list->bucket[node->next]);
 }
 return LRU_SUCCESS;
}
int lru_clear(PLRU_LIST list)
{
 PLRU_NODE tempnode,node;
 unsigned long j;
 if(list->head == -1)
 return LRU_SUCCESS;
 tempnode = &(list->bucket[list->head]);
 while(tempnode)
 {
 if(tempnode->next == -1)
 node = NULL;
 else
 node = &(list->bucket[tempnode->next]);
 if(list->freelist == -1)
 {
 list->freelist = tempnode->id;
 tempnode->free = 1;
 tempnode->key = -1;
 tempnode->next = -1;
 tempnode->prev = -1;
 }
 else
 {
 tempnode->next = list->freelist;
 list->bucket[list->freelist].prev = tempnode->id;
 list->freelist = tempnode->id;
 tempnode->prev = -1;
 tempnode->key = -1;
 tempnode->free = 1;
 }
 tempnode = node;
 }
 for(j = 0; j < list->hash.basesize; j++)
 {
 list->hash.base[j] = -1;
 }
 list->head = -1;
 list->tail = -1;
 list->usage = 0;
 return LRU_SUCCESS; 
}

void main()
{
 int key_table[LRU_SIZE];
 int i, usage;
 PLRU_LIST list;
 PLRU_NODE node;
 srand(time(NULL));
 for(i = 0; i < LRU_SIZE; i++)
 {
 key_table[i] = rand() % LRU_SIZE;
 printf("key_table[%d]:%d\n", i, key_table[i]);
 }
 list = (PLRU_LIST)malloc(sizeof(LRU_LIST));
 if(!list)
 printf("malloc error!\n");
 lru_init(list, LRU_SIZE, HASH_BASE);
 //node = (PLRU_NODE)malloc(sizeof(LRU_NODE));
 //if(!node)
 //printf("malloc error!\n");
 for(i = 0; i < LRU_SIZE; i++)
 {
 if(LRU_MISS == lru_inquiry(list, key_table[i]))
 {
 if(LRU_SUCCESS == lru_getfree(list, &node))
 {
 node->key =key_table[i];
 lru_insert(list, node);
 }
 else
 {
 printf("lru_getfree:get no free node!\n");
 }
 }
 else
 {
 printf("***************\n");
 printf("lru_inquiry hit key_table[%d]:%d\n", i, key_table[i]);
 lru_show(list);
 }
 }
 lru_show(list);
 usage = list->usage;
 for(i = 0; i < usage; i++)
 {
 lru_delete(list);
 printf("after delete one node!\n");
 lru_show(list);
 }
 lru_clear(list);
}
### 实现LRU缓存机制中双向链表的头尾指针变化 为了满足最常使用的操作(`get` 和 `put`)平均时间复杂度为O(1)[^5]的要求,LRU缓存通常采用哈希表配合双向链表的方式实现。其中: #### 双向链表的作用 - **头部节点**代表最近访问的数据项。 - **尾部节点**表示最久未被访问的数据项。 当执行特定的操作时,会涉及到调整这两个特殊节点的位置关系以及它们所指向的具体数据项的变化情况如下: #### 添加新元素至缓存内 如果当前容量尚未达到上限,则直接创建新的节点并将之插入到链表前端作为最新的访问记录;此时只需更新前驱结点该新增加单元之间的连接即可完成整个过程[^2]。 ```java // 插入节点到双向链表头部 private void addToHead(Node node) { node.prev = head; node.next = head.next; head.next.prev = node; head.next = node; } ``` #### 移除已有元素 对于超出设定大小限制的情况或是显式调用了删除接口的情形下,应当移除位于列表末端的那个条目——因为它对应着距离现在最远的一次读取/写入动作[^3]。 ```java // 删除指定节点 private void removeNode(Node node){ node.prev.next = node.next; node.next.prev = node.prev; } // 尾删法淘汰最不常用的页面 private void popTail(){ removeNode(tail.prev); } ``` #### 更新现有元素的状态 每当某个已存在于缓存里的key再次发生交互行为之后,就需要将其重新定位成最新一次触及的对象。这一步骤具体表现为先断开旧有的前后关联再依照前述方法挂载于队首位置之上[^4]。 ```java // 改变一个已经存在的节点成为最近使用过的节点 private void moveToHead(Node node){ removeNode(node); // 先把node从原来的地方摘下来 addToHead(node); // 再把它放到head后面去 } ``` 通过以上方式可以有效地管理内存资源分配的同时也保证了高效的查询性能表现[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值