LRU

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;
}

参考:https://mp.weixin.qq.com/s?__biz=MjM5MjAwODM4MA==&mid=2650719941&idx=2&sn=566c93fc8b2793024beab2591d190d4f&chksm=bea6b71689d13e00fe382bad6b9ed804940930af2b04482ac95f9e92b95fec5ecfa875229c87&mpshare=1&scene=23&srcid=#rd

http://www.cnblogs.com/cpselvis/p/6272096.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值