LRU缓存策略设计

一、什么是LRU缓存策略
LRU(Least Recently Used)近期最少使用算法。它的原理就是,缓存一定量的数据,当缓存数量超过设置的阈值时就删除一部分旧的数据。
那么我们怎样判定旧数据呢???根据局部性原理,距离当前最久没有被访问过的数据应该被淘汰。

二、LRU缓存策略实现原理
1、使用双向链表记录数据的被使用的时间
因为我们要删除最久没有被访问的数据,为了保证效率,我们在缓存中维护一个双向链表,该链表将缓存中的数据按照访问时间从新到旧排列起来。
当我们需要访问一个数据的时候,如果缓存中已经缓存了这个数据,那么我们就将该数据从缓存的双向链表中摘除,然后再重新放入到双向链表的表头。
如果缓存中没有我们需要的数据,那么我们可以在外部获得数据,然后将数据放入到缓存中,此过程中会将新数据插入到双向链表的表头。

2、使用hash表保证缓存中数据的访问速度
因为链表进行查找是O(n),比较慢。因此为了提高效率,我们除了将数据维护在一个双向链表中,同时还将数据维护在一个hash表中,这时我们访问的效率就变成了O(1)。

3、原理如图:
这里写图片描述
首先明白,hash表和双向链表用的是同一个结点。双向链表按照访问时间将这些结点串起来,就是图中黑色的箭头。hash表按照key值将这些结点串起来,并且使用拉链法解决冲突,比如紫色的箭头。

4、向缓存中插入一个结点
首先到hash表中去找:
如果找到的话,再去双向链表中将这个结点移到双向链表的头部。
如果没找到的话,则将这个数据插入到双向链表的头部,同时也插入到hash表中。如果这时候缓存的数量已经超过阈值,则就将双向链表的最后一个结点从链表中移除(注意不是删除),然后再将这个移除的结点,从hash表也移除,最后再删除这个结点。因为hash表和双向链表使用的是同一个结点,所以必须等到两边都移除后才能删除。

三、模拟实现LRU缓存
1、缓存的结点类型
//缓存链表、hash表的结点类型

template<typename K, typename T>
struct cacheNode
{
       K key;
       T data;
       struct cacheNode<K, T> *hashListPrev;  //指向hash链表的前一个结点
       struct cacheNode<K, T> *hashListNext;  //指向hash链表的后一个结点
       struct cacheNode<K, T> *lruListPrev;   //指向缓存双向链表的前一个结点
       struct cacheNode<K, T> *lruListNext;   //指向缓存双向链表的后一个结点
       cacheNode(K k = K(), T d = T())
              :key(k)
              , data(d)
              , hashListPrev(NULL)
              , hashListNext(NULL)
              , lruListPrev(NULL)
              , lruListNext(NULL)
       {}
};
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

可以看到,缓存的结点中有四个指针,其中两个用来维护双向链表,另外两个维护hash链表。本质上来说LRU缓存就是一个复杂链表,我们通过这种数据结构来达到缓存的目的。

2、LRU类的设计
为了提高代码的复用性,我们将LRU设计成类模板。

template<typename K>
struct isEqual                 //定义key值的比较方式,相等返回true
{
       bool operator()(const K left,const K right)
       {
              if (left == right)
                     return true;
              else
                     return false;
       }
};


//缓存类
template<typename K, typename T,class Compare=isEqual<K> >
class lruCache
{
       typedef cacheNode<K, T> Node;
public:
       lruCache(size_t capacity);
       ~lruCache();
       void lruCacheSet(K key, T data);  //向缓存中放入数据
       bool lruCacheGet(K key,T& data);  //从缓存中得到数据
private:
       void RemoveFromList(Node* post);    //从双向链表中删除指定结点
       cacheNode<K, T>* InsertToListHead(Node* node);  //向双向链表的表头插入数据
       void UpdateLRUList(Node* node);
       void DeleteList(Node* head);        //删除整个双向链表   
       int HashFunc(K key);           //获取hash值
       cacheNode<K, T>* GetValueFromHashMap(K key);  //从hashmap中获取一个缓存单元
       void InsertValueToHashMap(Node* node); //插入一个缓存单元到hashmap
       void RemoveFromeHashMap(Node* node);  //从hashmap中删除一个缓存单元
private:
       size_t _capacity;     //缓存的容量
       Node** _hashMap;      //hash数组,
       Node* _lruListHead;    //指向缓存双向链表的头部
       Node* _lruListTail;    //指向缓存双向链表的尾部
       size_t _lruListSize;   //记录双向链表结点中的个数
};

    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

3、项目源码
LRU源码

(function () { ('pre.prettyprint code').each(function () { var lines = (this).text().split(\n).length;var numbering = $('
    ').addClass('pre-numbering').hide(); (this).addClass(hasnumbering).parent().append( numbering); for (i = 1; i

    一、什么是LRU缓存策略
    LRU(Least Recently Used)近期最少使用算法。它的原理就是,缓存一定量的数据,当缓存数量超过设置的阈值时就删除一部分旧的数据。
    那么我们怎样判定旧数据呢???根据局部性原理,距离当前最久没有被访问过的数据应该被淘汰。

    二、LRU缓存策略实现原理
    1、使用双向链表记录数据的被使用的时间
    因为我们要删除最久没有被访问的数据,为了保证效率,我们在缓存中维护一个双向链表,该链表将缓存中的数据按照访问时间从新到旧排列起来。
    当我们需要访问一个数据的时候,如果缓存中已经缓存了这个数据,那么我们就将该数据从缓存的双向链表中摘除,然后再重新放入到双向链表的表头。
    如果缓存中没有我们需要的数据,那么我们可以在外部获得数据,然后将数据放入到缓存中,此过程中会将新数据插入到双向链表的表头。

    2、使用hash表保证缓存中数据的访问速度
    因为链表进行查找是O(n),比较慢。因此为了提高效率,我们除了将数据维护在一个双向链表中,同时还将数据维护在一个hash表中,这时我们访问的效率就变成了O(1)。

    3、原理如图:
    这里写图片描述
    首先明白,hash表和双向链表用的是同一个结点。双向链表按照访问时间将这些结点串起来,就是图中黑色的箭头。hash表按照key值将这些结点串起来,并且使用拉链法解决冲突,比如紫色的箭头。

    4、向缓存中插入一个结点
    首先到hash表中去找:
    如果找到的话,再去双向链表中将这个结点移到双向链表的头部。
    如果没找到的话,则将这个数据插入到双向链表的头部,同时也插入到hash表中。如果这时候缓存的数量已经超过阈值,则就将双向链表的最后一个结点从链表中移除(注意不是删除),然后再将这个移除的结点,从hash表也移除,最后再删除这个结点。因为hash表和双向链表使用的是同一个结点,所以必须等到两边都移除后才能删除。

    三、模拟实现LRU缓存
    1、缓存的结点类型
    //缓存链表、hash表的结点类型

    template<typename K, typename T>
    struct cacheNode
    {
           K key;
           T data;
           struct cacheNode<K, T> *hashListPrev;  //指向hash链表的前一个结点
           struct cacheNode<K, T> *hashListNext;  //指向hash链表的后一个结点
           struct cacheNode<K, T> *lruListPrev;   //指向缓存双向链表的前一个结点
           struct cacheNode<K, T> *lruListNext;   //指向缓存双向链表的后一个结点
           cacheNode(K k = K(), T d = T())
                  :key(k)
                  , data(d)
                  , hashListPrev(NULL)
                  , hashListNext(NULL)
                  , lruListPrev(NULL)
                  , lruListNext(NULL)
           {}
    };
      
      
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    可以看到,缓存的结点中有四个指针,其中两个用来维护双向链表,另外两个维护hash链表。本质上来说LRU缓存就是一个复杂链表,我们通过这种数据结构来达到缓存的目的。

    2、LRU类的设计
    为了提高代码的复用性,我们将LRU设计成类模板。

    template<typename K>
    struct isEqual                 //定义key值的比较方式,相等返回true
    {
           bool operator()(const K left,const K right)
           {
                  if (left == right)
                         return true;
                  else
                         return false;
           }
    };
    
    
    //缓存类
    template<typename K, typename T,class Compare=isEqual<K> >
    class lruCache
    {
           typedef cacheNode<K, T> Node;
    public:
           lruCache(size_t capacity);
           ~lruCache();
           void lruCacheSet(K key, T data);  //向缓存中放入数据
           bool lruCacheGet(K key,T& data);  //从缓存中得到数据
    private:
           void RemoveFromList(Node* post);    //从双向链表中删除指定结点
           cacheNode<K, T>* InsertToListHead(Node* node);  //向双向链表的表头插入数据
           void UpdateLRUList(Node* node);
           void DeleteList(Node* head);        //删除整个双向链表   
           int HashFunc(K key);           //获取hash值
           cacheNode<K, T>* GetValueFromHashMap(K key);  //从hashmap中获取一个缓存单元
           void InsertValueToHashMap(Node* node); //插入一个缓存单元到hashmap
           void RemoveFromeHashMap(Node* node);  //从hashmap中删除一个缓存单元
    private:
           size_t _capacity;     //缓存的容量
           Node** _hashMap;      //hash数组,
           Node* _lruListHead;    //指向缓存双向链表的头部
           Node* _lruListTail;    //指向缓存双向链表的尾部
           size_t _lruListSize;   //记录双向链表结点中的个数
    };
    
      
      
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    3、项目源码
    LRU源码

    (function () { ('pre.prettyprint code').each(function () { var lines = (this).text().split(\n).length;var numbering = $('
      ').addClass('pre-numbering').hide(); (this).addClass(hasnumbering).parent().append( numbering); for (i = 1; i

      猜你喜欢

      转载自blog.csdn.net/shanchuan94912/article/details/72843235
      今日推荐