缓存策略之LRU实现(基于双链表实现)

来源:互联网 发布:照片打分软件 编辑:程序博客网 时间:2024/05/01 11:39

缓存在应用中的作用,相信不用多说,对性能是具有质的提升的,而目前的缓存策略常用的FIFO,LRU等等。

   今天来探讨一下 LRU这种缓存策略的底层原理与实现。

  首先,来看看LRU的定义: Least recently used. 可以理解为, 最少使用的被淘汰。  

  今天主要来讨论基于双链表的LRU算法的实现, 在讨论之前,我们需要了解一下,传统LRU算法的实现,与其的弊端.

 

   传统意义的LRU算法是为每一个Cache对象设置一个计数器,每次Cache命中则给计数器+1,而Cache用完,需要淘汰旧内容,放置新内容时,就查看所有的计数器,并将最少使用的内容替换掉。

    它的弊端很明显,如果Cache的数量少,问题不会很大, 但是如果Cache的空间过大,达到10W或者100W以上,一旦需要淘汰,则需要遍历所有计算器,其性能与资源消耗是巨大的。效率也就非常的慢了。

 

    基于这样的情况,所有就有新的LRU算法的实现----基于双链表 的LRU实现。

    它的原理: 将Cache的所有位置都用双连表连接起来,当一个位置被命中之后,就将通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cache直接加到链表头中。

     这样,在多次进行Cache操作后,最近被命中的,就会被向链表头方向移动,而没有命中的,而想链表后面移动,链表尾则表示最近最少使用的Cache。

     当需要替换内容时候,链表的最后位置就是最少被命中的位置,我们只需要淘汰链表最后的部分即可。

 

  上面说了这么多的理论, 下面用代码来实现一个LRU策略的缓存。

    我们用一个对象来表示Cache,并实现双链表,

     

Java代码  
  1. public class LRUCache {  
  2.     /** 
  3.      * 链表节点 
  4.      * @author Administrator 
  5.      * 
  6.      */  
  7.     class CacheNode {  
  8.         ……  
  9.     }  
  10.   
  11.     private int cacheSize;//缓存大小  
  12.     private Hashtable nodes;//缓存容器  
  13.     private int currentSize;//当前缓存对象数量  
  14.     private CacheNode first;//(实现双链表)链表头  
  15.     private CacheNode last;//(实现双链表)链表尾  
  16. }  

 

 下面给出完整的实现,这个类也被Tomcat所使用( org.apache.tomcat.util.collections.LRUCache),但是在tomcat6.x版本中,已经被弃用,使用另外其他的缓存类来替代它。

 

Java代码  
  1. public class LRUCache {  
  2.     /** 
  3.      * 链表节点 
  4.      * @author Administrator 
  5.      * 
  6.      */  
  7.     class CacheNode {  
  8.         CacheNode prev;//前一节点  
  9.         CacheNode next;//后一节点  
  10.         Object value;//值  
  11.         Object key;//键  
  12.         CacheNode() {  
  13.         }  
  14.     }  
  15.   
  16.     public LRUCache(int i) {  
  17.         currentSize = 0;  
  18.         cacheSize = i;  
  19.         nodes = new Hashtable(i);//缓存容器  
  20.     }  
  21.       
  22.     /** 
  23.      * 获取缓存中对象 
  24.      * @param key 
  25.      * @return 
  26.      */  
  27.     public Object get(Object key) {  
  28.         CacheNode node = (CacheNode) nodes.get(key);  
  29.         if (node != null) {  
  30.             moveToHead(node);  
  31.             return node.value;  
  32.         } else {  
  33.             return null;  
  34.         }  
  35.     }  
  36.       
  37.     /** 
  38.      * 添加缓存 
  39.      * @param key 
  40.      * @param value 
  41.      */  
  42.     public void put(Object key, Object value) {  
  43.         CacheNode node = (CacheNode) nodes.get(key);  
  44.           
  45.         if (node == null) {  
  46.             //缓存容器是否已经超过大小.  
  47.             if (currentSize >= cacheSize) {  
  48.                 if (last != null)//将最少使用的删除  
  49.                     nodes.remove(last.key);  
  50.                 removeLast();  
  51.             } else {  
  52.                 currentSize++;  
  53.             }  
  54.               
  55.             node = new CacheNode();  
  56.         }  
  57.         node.value = value;  
  58.         node.key = key;  
  59.         //将最新使用的节点放到链表头,表示最新使用的.  
  60.         moveToHead(node);  
  61.         nodes.put(key, node);  
  62.     }  
  63.   
  64.     /** 
  65.      * 将缓存删除 
  66.      * @param key 
  67.      * @return 
  68.      */  
  69.     public Object remove(Object key) {  
  70.         CacheNode node = (CacheNode) nodes.get(key);  
  71.         if (node != null) {  
  72.             if (node.prev != null) {  
  73.                 node.prev.next = node.next;  
  74.             }  
  75.             if (node.next != null) {  
  76.                 node.next.prev = node.prev;  
  77.             }  
  78.             if (last == node)  
  79.                 last = node.prev;  
  80.             if (first == node)  
  81.                 first = node.next;  
  82.         }  
  83.         return node;  
  84.     }  
  85.   
  86.     public void clear() {  
  87.         first = null;  
  88.         last = null;  
  89.     }  
  90.   
  91.     /** 
  92.      * 删除链表尾部节点 
  93.      *  表示 删除最少使用的缓存对象 
  94.      */  
  95.     private void removeLast() {  
  96.         //链表尾不为空,则将链表尾指向null. 删除连表尾(删除最少使用的缓存对象)  
  97.         if (last != null) {  
  98.             if (last.prev != null)  
  99.                 last.prev.next = null;  
  100.             else  
  101.                 first = null;  
  102.             last = last.prev;  
  103.         }  
  104.     }  
  105.       
  106.     /** 
  107.      * 移动到链表头,表示这个节点是最新使用过的 
  108.      * @param node 
  109.      */  
  110.     private void moveToHead(CacheNode node) {  
  111.         if (node == first)  
  112.             return;  
  113.         if (node.prev != null)  
  114.             node.prev.next = node.next;  
  115.         if (node.next != null)  
  116.             node.next.prev = node.prev;  
  117.         if (last == node)  
  118.             last = node.prev;  
  119.         if (first != null) {  
  120.             node.next = first;  
  121.             first.prev = node;  
  122.         }  
  123.         first = node;  
  124.         node.prev = null;  
  125.         if (last == null)  
  126.             last = first;  
  127.     }  
  128.     private int cacheSize;  
  129.     private Hashtable nodes;//缓存容器  
  130.     private int currentSize;  
  131.     private CacheNode first;//链表头  
  132.     private CacheNode last;//链表尾  
  133. }