LRU全称Least Recently Used,也就是最近最少最少使用的意思,是一种内存管理算法,最早利用与Linux操作系统中。
LRU算法基于一种假设:长期不被使用的数据,在未来使用的几率也不大。因此数据内存达到一定的阈值时,我们要移除最近最少被使用的数据。
如果现在有一个用户系统(使用哈希表存储用户信息),但是由于用户太多导致服务器宕机了,我们可以使用LRU算法,LRU算法中存在一种数据结构叫做哈希链表。
什么事哈希链表呢?
我们都知道,哈希表是有多个key-value组成。在“逻辑”上,这些key-value是无所谓排列顺序的,谁先谁后都是一样的
在哈希链表中,这些key-value不再是彼此无关存在的,而是被一个链条串了起来。每一个key-value都有它的前驱key-value和后继key-value,就像双向链表中的节点一样。
这样一来原本无序的哈希表拥有固定的排列顺序。
以用户信息的需求为例,演示下LRU算法的基本思路:
1.假设我们使用哈希链表来缓存用户信息,目前缓存了4个用户,这4个用户是按照时间顺序依次从链表右端插入的。
2.此时,业务方访问用户5,由于哈希链表中没有用户5的数据,我们从数据库中读取出来,插入到缓存当中。这时候,链表中最右端是最新访问到的用户5,最左端是最近最少访问的用户1。
3.接下来,业务方访问用户2,哈希链表中存在用户2的数据,我们怎么做呢?我们把用户2从它的前驱节点和后继节点之间移除,重新插入到链表最右端。这时候,链表中最右端变成了最新访问到的用户2,最左端仍然是最近最少访问的用户1。
4.接下来,业务方请求修改用户4的信息。同样道理,我们把用户4从原来的位置移动到链表最右侧,并把用户信息的值更新。这时候,链表中最右端是最新访问到的用户4,最左端仍然是最近最少访问的用户1。
5.后来业务方换口味了,访问用户6,用户6在缓存里没有,需要插入到哈希链表。假设这时候缓存容量已经达到上限,必须先删除最近最少访问的数据,那么位于哈希链表最左端的用户1就会被删除掉,然后再把用户6插入到最右端。
#include<iostream>
#include<map>
using namespace std;
/*
LRU实现采用双向链表 + Map 来进行实现。
这里采用双向链表的原因是:如果采用普通的单链表,
则删除节点的时候需要从表头开始遍历查找,效率为O(n),
采用双向链表可以直接改变节点的前驱的指针指向进行删除达到O(1)的效率。
使用Map来保存节点的key、value值便于能在O(logN)的时间查找元素,对应get操作。
*/
//双链表节点的定义:
struct CacheNode {
int key;
int value;
CacheNode *pre, *next;
CacheNode(int k, int v) : key(k), value(v), pre(NULL), next(NULL) {}
};
class LRUCache{
private:
int size; // Maximum of cachelist size.
CacheNode *head, *tail;
map<int, CacheNode *> mp; // Use hashmap to store
public:
//对于LRUCache这个类而言,构造函数需要指定容量大小
LRUCache(int capacity)
{
size = capacity;
head = NULL;
tail = NULL;
}
//get(key)操作的实现比较简单,直接通过判断Map是否含有key值即可,如果查找到key,则返回对应的value,否则返回-1;
int get(int key)
{
map<int, CacheNode *>::iterator it = mp.find(key);
if (it != mp.end())
{
CacheNode *node = it -> second;
remove(node);
setHead(node);
return node -> value;
}
else
{
return -1;
}
}
//set(key, value)操作需要分情况判断。如果当前的key值对应的节点已经存在,则将这个节点取出来,并且删除节点所处的原有的位置,并在头部插入该节点;如果节点不存在节点中,这个时候需要在链表的头部插入新节点,插入新节点可能导致容量溢出,如果出现溢出的情况,则需要删除链表尾部的节点。
void set(int key, int value)
{
map<int, CacheNode *>::iterator it = mp.find(key);
if (it != mp.end())
{
CacheNode *node = it -> second;
node -> value = value;
remove(node);
setHead(node);
}
else
{
CacheNode *newNode = new CacheNode(key, value);
if (mp.size() >= size)
{
map<int, CacheNode *>::iterator iter = mp.find(tail -> key);
remove(tail);
mp.erase(iter);
}
setHead(newNode);
mp[key] = newNode;
}
}
//双链表的节点删除操作:
void remove(CacheNode *node)
{
if (node -> pre != NULL)
{
node -> pre -> next = node -> next;
}
else
{
head = node -> next;
}
if (node -> next != NULL)
{
node -> next -> pre = node -> pre;
}
else
{
tail = node -> pre;
}
}
//将节点插入到头部的操作:
void setHead(CacheNode *node)
{
node -> next = head;
node -> pre = NULL;
if (head != NULL)
{
head -> pre = node;
}
head = node;
if (tail == NULL)
{
tail = head;
}
}
};
int main(int argc, char **argv)
{
LRUCache *lruCache = new LRUCache(2);
lruCache -> set(2, 1);
lruCache -> set(1, 1);
cout << lruCache -> get(2) << endl;
lruCache -> set(4, 1);
cout << lruCache -> get(1) << endl;
cout << lruCache -> get(2) << endl;
}