簡單的java緩存實現(LRU,LFU,FIFO)

原帖地址:http://my.oschina.net/u/866190/blog/188712

提到緩存,不得不提就是緩存算法(淘汰算法),常見算法有LRU、LFU和FIFO等算法,每種算法各有各的優勢和缺點及適應環境。

1、LRU(Least Recently Used ,最近最少使用)
算法根據數據的最近訪問記錄來淘汰數據,其原理是如果數據最近被訪問過,將來被訪問的幾概率相對比較高,最常見的實現是使用一個鏈表保存緩存數據,詳細具體算法如下:
1. 新數據插入到鏈表頭部;
2. 每當緩存數據命中,則將數據移到鏈表頭部;
3. 當鏈表滿的時候,將鏈表尾部的數據丟棄;


2、LFU(Least Frequently Used,最不經常使用)
算法根據數據的歷史訪問頻率來淘汰數據,其原理是如果數據過去被訪問次數越多,將來被訪問的幾概率相對比較高。LFU的每個數據塊都有一個引用計數,所有數據塊按照引用計數排序,具有相同引用計數的數據塊則按照時間排序。
具體算法如下:
1. 新加入數據插入到隊列尾部(因爲引用計數爲1);
2. 隊列中的數據被訪問後,引用計數增加,隊列重新排序;
3. 當需要淘汰數據時,將已經排序的列表最後的數據塊刪除;


3、FIFO(First In First Out ,先進先出)
算法是根據先進先出原理來淘汰數據的,實現上是最簡單的一種,具體算法如下:
1. 新訪問的數據插入FIFO隊列尾部,數據在FIFO隊列中順序移動;
2. 淘汰FIFO隊列頭部的數據;


評價一個緩存算法好壞的標準主要有兩個,一是命中率要高,二是算法要容易實現。當存在熱點數據時,LRU的效率很好,但偶發性的、週期性的批量操作會導致LRU命中率急劇下降,緩存污染情況比較嚴重。LFU效率要優於LRU,且能夠避免週期性或者偶發性的操作導致緩存命中率下降的問題。但LFU需要記錄數據的歷史訪問記錄,一旦數據訪問模式改變,LFU需要更長時間來適用新的訪問模式,即:LFU存在歷史數據影響將來數據的“緩存污染”效用。FIFO雖然實現很簡單,但是命中率很低,實際上也很少使用這種算法。

基於現有jdk類庫,我們可以很容易實現上面的緩存算法

首先定義緩存接口類

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * 緩存接口 
  3.  * @author Wen 
  4.  * 
  5.  */  
  6. public interface Cache<K,V> {  
  7.     /** 
  8.      * 返回當前緩存的大小 
  9.      *  
  10.      * @return   
  11.      */  
  12.     int size();  
  13.       
  14.     /** 
  15.      * 返回默認存活時間 
  16.      *  
  17.      * @return 
  18.      */  
  19.     long getDefaultExpire();  
  20.       
  21.     /** 
  22.      * 向緩存添加value對象,其在緩存中生存時間爲默認值 
  23.      *  
  24.      * @param key 
  25.      * @param value 
  26.      */  
  27.     void put(K key ,V value) ;  
  28.       
  29.     /** 
  30.      * 向緩存添加value對象,並指定存活時間 
  31.      * @param key 
  32.      * @param value 
  33.      * @param expire  過期時間 
  34.      */  
  35.     void put(K key ,V value , long expire ) ;  
  36.       
  37.     /** 
  38.      * 查找緩存對象 
  39.      * @param key 
  40.      * @return 
  41.      */  
  42.     V get(K key);  
  43.       
  44.     /** 
  45.      * 淘汰對象 
  46.      *  
  47.      * @return  被刪除對象大小 
  48.      */  
  49.     int eliminate();  
  50.       
  51.     /** 
  52.      * 緩存是否已經滿 
  53.      * @return 
  54.      */  
  55.     boolean isFull();  
  56.   
  57.     /** 
  58.      * 刪除緩存對象 
  59.      *  
  60.      * @param key 
  61.      */  
  62.     void remove(K key);  
  63.   
  64.     /** 
  65.      * 清除所有緩存對象 
  66.      */  
  67.     void clear();  
  68.   
  69.     /** 
  70.      * 返回緩存大小 
  71.      *  
  72.      * @return   
  73.      */  
  74.     int getCacheSize();  
  75.   
  76.     /** 
  77.      * 緩存中是否爲空 
  78.      */  
  79.     boolean isEmpty();  
  80.   
  81. }  

基本實現抽象類

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. import java.util.Map;  
  2. import java.util.concurrent.locks.Lock;  
  3. import java.util.concurrent.locks.ReentrantReadWriteLock;  
  4.   
  5. /** 
  6.  * 默認實現 
  7.  */  
  8. public abstract class AbstractCacheMap<K,V> implements Cache<K,V> {  
  9.   
  10.     class CacheObject<K2,V2> {  
  11.         CacheObject(K2 key, V2 value, long ttl) {  
  12.             this.key = key;  
  13.             this.cachedObject = value;  
  14.             this.ttl = ttl;  
  15.             this.lastAccess = System.currentTimeMillis();  
  16.         }  
  17.   
  18.         final K2 key;  
  19.         final V2 cachedObject;  
  20.         long lastAccess;        // 最後訪問時間  
  21.         long accessCount;       // 訪問次數  
  22.         long ttl;               // 對象存活時間(time-to-live)  
  23.   
  24.         boolean isExpired() {  
  25.             if (ttl == 0) {  
  26.                 return false;  
  27.             }  
  28.             return lastAccess + ttl < System.currentTimeMillis();  
  29.         }  
  30.         V2 getObject() {  
  31.             lastAccess = System.currentTimeMillis();  
  32.             accessCount++;  
  33.             return cachedObject;  
  34.         }  
  35.     }  
  36.   
  37.     protected Map<K,CacheObject<K,V>> cacheMap;  
  38.   
  39.     private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock();  
  40.     private final Lock readLock = cacheLock.readLock();  
  41.     private final Lock writeLock = cacheLock.writeLock();  
  42.   
  43.   
  44.   
  45.     protected int cacheSize;      // 緩存大小 , 0 -> 無限制  
  46.       
  47.     protected  boolean existCustomExpire ; //是否設置默認過期時間  
  48.       
  49.     public int getCacheSize() {  
  50.         return cacheSize;  
  51.     }  
  52.   
  53.     protected long defaultExpire;     // 默認過期時間, 0 -> 永不過期  
  54.       
  55.     public AbstractCacheMap(int cacheSize ,long defaultExpire){  
  56.         this.cacheSize  = cacheSize ;  
  57.         this.defaultExpire  = defaultExpire ;  
  58.     }  
  59.   
  60.       
  61.     public long getDefaultExpire() {  
  62.         return defaultExpire;  
  63.     }  
  64.   
  65.   
  66.     protected boolean isNeedClearExpiredObject(){  
  67.         return defaultExpire > 0 || existCustomExpire ;  
  68.     }  
  69.   
  70.       
  71.     public void put(K key, V value) {  
  72.         put(key, value, defaultExpire );  
  73.     }  
  74.   
  75.   
  76.     public void put(K key, V value, long expire) {  
  77.         writeLock.lock();  
  78.   
  79.         try {  
  80.             CacheObject<K,V> co = new CacheObject<K,V>(key, value, expire);  
  81.             if (expire != 0) {  
  82.                 existCustomExpire = true;  
  83.             }  
  84.             if (isFull()) {  
  85.                 eliminate() ;  
  86.             }  
  87.             cacheMap.put(key, co);  
  88.         }  
  89.         finally {  
  90.             writeLock.unlock();  
  91.         }  
  92.     }  
  93.   
  94.   
  95.   
  96.     /** 
  97.      * {@inheritDoc} 
  98.      */  
  99.     public V get(K key) {  
  100.         readLock.lock();  
  101.   
  102.         try {  
  103.             CacheObject<K,V> co = cacheMap.get(key);  
  104.             if (co == null) {  
  105.                 return null;  
  106.             }  
  107.             if (co.isExpired() == true) {  
  108.                 cacheMap.remove(key);  
  109.                 return null;  
  110.             }  
  111.   
  112.             return co.getObject();  
  113.         }  
  114.         finally {  
  115.             readLock.unlock();  
  116.         }  
  117.     }  
  118.       
  119.     public final int eliminate() {  
  120.         writeLock.lock();  
  121.         try {  
  122.             return eliminateCache();  
  123.         }  
  124.         finally {  
  125.             writeLock.unlock();  
  126.         }  
  127.     }  
  128.       
  129.     /** 
  130.      * 淘汰對象具體實現 
  131.      *  
  132.      * @return 
  133.      */  
  134.     protected abstract int eliminateCache();   
  135.   
  136.   
  137.       
  138.     public boolean isFull() {  
  139.         if (cacheSize == 0) {//o -> 無限制  
  140.             return false;  
  141.         }  
  142.         return cacheMap.size() >= cacheSize;  
  143.     }  
  144.   
  145.       
  146.     public void remove(K key) {  
  147.         writeLock.lock();  
  148.         try {  
  149.             cacheMap.remove(key);  
  150.         }  
  151.         finally {  
  152.             writeLock.unlock();  
  153.         }  
  154.     }  
  155.   
  156.       
  157.     public void clear() {  
  158.         writeLock.lock();  
  159.         try {  
  160.             cacheMap.clear();  
  161.         }  
  162.         finally {  
  163.             writeLock.unlock();  
  164.         }  
  165.     }  
  166.   
  167.     public int size() {  
  168.         return cacheMap.size();  
  169.     }  
  170.   
  171.       
  172.     public boolean isEmpty() {  
  173.         return size() == 0;  
  174.     }  
  175. }  

LRU緩存實現類

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. import java.util.Iterator;  
  2. import java.util.LinkedHashMap;  
  3. import java.util.Map;  
  4.   
  5. /**  
  6.  * LRU  實現  
  7.  * @author Wen  
  8.  *  
  9.  * @param <K>  
  10.  * @param <V>  
  11.  */  
  12. public class LRUCache<K, V> extends AbstractCacheMap<K, V> {  
  13.   
  14.     public LRUCache(int cacheSize, long defaultExpire) {  
  15.           
  16.         super(cacheSize , defaultExpire) ;  
  17.   
  18.         //linkedHash已經實現LRU算法 是通過雙向鏈表來實現,當某個位置被命中,通過調整鏈表的指向將該位置調整到頭位置,新加入的內容直接放在鏈表頭,如此一來,最近被命中的內容就向鏈表頭移動,需要替換時,鏈表最後的位置就是最近最少使用的位置  
  19.         this.cacheMap = new LinkedHashMap<K, CacheObject<K, V>>( cacheSize +1 , 1f,true ) {  
  20.   
  21.             @Override  
  22.             protected boolean removeEldestEntry(  
  23.                     Map.Entry<K, CacheObject<K, V>> eldest) {  
  24.   
  25.                 return LRUCache.this.removeEldestEntry(eldest);  
  26.             }  
  27.   
  28.         };  
  29.     }  
  30.   
  31.     private boolean removeEldestEntry(Map.Entry<K, CacheObject<K, V>> eldest) {  
  32.   
  33.         if (cacheSize == 0)  
  34.             return false;  
  35.   
  36.         return size() > cacheSize;  
  37.     }  
  38.   
  39.     /**  
  40.      * 只需要實現清除過期對象就可以了,linkedHashMap已經實現LRU  
  41.      */  
  42.     @Override  
  43.     protected int eliminateCache() {  
  44.   
  45.         if(!isNeedClearExpiredObject()){ return 0 ;}  
  46.           
  47.         Iterator<CacheObject<K, V>> iterator = cacheMap.values().iterator();  
  48.         int count  = 0 ;  
  49.         while(iterator.hasNext()){  
  50.             CacheObject<K, V> cacheObject = iterator.next();  
  51.               
  52.             if(cacheObject.isExpired() ){  
  53.                 iterator.remove();   
  54.                 count++ ;  
  55.             }  
  56.         }  
  57.           
  58.         return count;  
  59.     }  
  60.   
  61. }  

LFU實現類

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. import java.util.HashMap;  
  2. import java.util.Iterator;  
  3.   
  4. //LFU實現  
  5. public class LFUCache<K,V> extends AbstractCacheMap<K, V> {  
  6.       
  7.   
  8.     public LFUCache(int cacheSize, long defaultExpire) {  
  9.         super(cacheSize, defaultExpire);  
  10.         cacheMap = new HashMap<K, CacheObject<K,V>>(cacheSize+1) ;  
  11.     }  
  12.   
  13.     /** 
  14.      * 實現刪除過期對象 和 刪除訪問次數最少的對象  
  15.      *  
  16.      */  
  17.     @Override  
  18.     protected int eliminateCache() {  
  19.         Iterator<CacheObject<K, V>> iterator = cacheMap.values().iterator();  
  20.         int count  = 0 ;  
  21.         long minAccessCount = Long.MAX_VALUE  ;  
  22.         while(iterator.hasNext()){  
  23.             CacheObject<K, V> cacheObject = iterator.next();  
  24.               
  25.             if(cacheObject.isExpired() ){  
  26.                 iterator.remove();   
  27.                 count++ ;  
  28.                 continue ;  
  29.             }else{  
  30.                 minAccessCount  = Math.min(cacheObject.accessCount , minAccessCount)  ;  
  31.             }  
  32.         }  
  33.           
  34.         if(count > 0 ) return count ;  
  35.           
  36.         if(minAccessCount != Long.MAX_VALUE ){  
  37.               
  38.             iterator = cacheMap.values().iterator();  
  39.               
  40.             while(iterator.hasNext()){  
  41.                 CacheObject<K, V> cacheObject = iterator.next();  
  42.                   
  43.                 cacheObject.accessCount  -=  minAccessCount ;  
  44.                   
  45.                 if(cacheObject.accessCount <= 0 ){  
  46.                     iterator.remove();  
  47.                     count++ ;  
  48.                 }  
  49.                   
  50.             }  
  51.               
  52.         }  
  53.           
  54.         return count;  
  55.     }  
  56.   
  57. }  

FIFO實現類

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. import java.util.Iterator;  
  2. import java.util.LinkedHashMap;  
  3. /** 
  4.  * FIFO實現 
  5.  * @author Wen 
  6.  * 
  7.  * @param <K> 
  8.  * @param <V> 
  9.  */  
  10. public class FIFOCache<K, V> extends AbstractCacheMap<K, V> {  
  11.   
  12.     public FIFOCache(int cacheSize, long defaultExpire) {  
  13.         super(cacheSize, defaultExpire);  
  14.         cacheMap = new LinkedHashMap<K, CacheObject<K, V>>(cacheSize + 1);  
  15.     }  
  16.   
  17.     @Override  
  18.     protected int eliminateCache() {  
  19.   
  20.         int count = 0;  
  21.         K firstKey = null;  
  22.   
  23.         Iterator<CacheObject<K, V>> iterator = cacheMap.values().iterator();  
  24.         while (iterator.hasNext()) {  
  25.             CacheObject<K, V> cacheObject = iterator.next();  
  26.   
  27.             if (cacheObject.isExpired()) {  
  28.                 iterator.remove();  
  29.                 count++;  
  30.             } else {  
  31.                 if (firstKey == null)  
  32.                     firstKey = cacheObject.key;  
  33.             }  
  34.         }  
  35.   
  36.         if (firstKey != null && isFull()) {//刪除過期對象還是滿,繼續刪除鏈表第一個  
  37.             cacheMap.remove(firstKey);  
  38.         }  
  39.   
  40.         return count;  
  41.     }  
  42.   
  43. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章