提到緩存,不得不提就是緩存算法(淘汰算法),常見算法有LRU、LFU和FIFO等算法,每種算法各有各的優點和缺點及適應環境。java
一、LRU(Least Recently Used ,最近最少使用)
算法根據數據的最近訪問記錄來淘汰數據,其原理是若是數據最近被訪問過,未來被訪問的几几率相對比較高,最多見的實現是使用一個鏈表保存緩存數據,詳細具體算法以下:
1. 新數據插入到鏈表頭部;
2. 每當緩存數據命中,則將數據移到鏈表頭部;
3. 當鏈表滿的時候,將鏈表尾部的數據丟棄;
二、LFU(Least Frequently Used,最不常常使用)
算法根據數據的歷史訪問頻率來淘汰數據,其原理是若是數據過去被訪問次數越多,未來被訪問的几几率相對比較高。LFU的每一個數據塊都有一個引用計數,全部數據塊按照引用計數排序,具備相同引用計數的數據塊則按照時間排序。
具體算法以下:
1. 新加入數據插入到隊列尾部(由於引用計數爲1);
2. 隊列中的數據被訪問後,引用計數增長,隊列從新排序;
3. 當須要淘汰數據時,將已經排序的列表最後的數據塊刪除;
三、FIFO(First In First Out ,先進先出)
算法是根據先進先出原理來淘汰數據的,實現上是最簡單的一種,具體算法以下:
1. 新訪問的數據插入FIFO隊列尾部,數據在FIFO隊列中順序移動;
2. 淘汰FIFO隊列頭部的數據;
評價一個緩存算法好壞的標準主要有兩個,一是命中率要高,二是算法要容易實現。當存在熱點數據時,LRU的效率很好,但偶發性的、週期性的批量操做會致使LRU命中率急劇降低,緩存污染狀況比較嚴重。LFU效率要優於LRU,且可以避免週期性或者偶發性的操做致使緩存命中率降低的問題。但LFU須要記錄數據的歷史訪問記錄,一旦數據訪問模式改變,LFU須要更長時間來適用新的訪問模式,即:LFU存在歷史數據影響未來數據的「緩存污染」效用。FIFO雖然實現很簡單,可是命中率很低,實際上也不多使用這種算法。算法
基於現有jdk類庫,咱們能夠很容易實現上面的緩存算法緩存
首先定義緩存接口類ide
/** * 緩存接口 * @author Wen * */ public interface Cache<K,V> { /** * 返回當前緩存的大小 * * @return */ int size(); /** * 返回默認存活時間 * * @return */ long getDefaultExpire(); /** * 向緩存添加value對象,其在緩存中生存時間爲默認值 * * @param key * @param value */ void put(K key ,V value) ; /** * 向緩存添加value對象,並指定存活時間 * @param key * @param value * @param expire 過時時間 */ void put(K key ,V value , long expire ) ; /** * 查找緩存對象 * @param key * @return */ V get(K key); /** * 淘汰對象 * * @return 被刪除對象大小 */ int eliminate(); /** * 緩存是否已經滿 * @return */ boolean isFull(); /** * 刪除緩存對象 * * @param key */ void remove(K key); /** * 清除全部緩存對象 */ void clear(); /** * 返回緩存大小 * * @return */ int getCacheSize(); /** * 緩存中是否爲空 */ boolean isEmpty(); }
基本實現抽象類this
import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 默認實現 */ public abstract class AbstractCacheMap<K,V> implements Cache<K,V> { class CacheObject<K2,V2> { CacheObject(K2 key, V2 value, long ttl) { this.key = key; this.cachedObject = value; this.ttl = ttl; this.lastAccess = System.currentTimeMillis(); } final K2 key; final V2 cachedObject; long lastAccess; // 最後訪問時間 long accessCount; // 訪問次數 long ttl; // 對象存活時間(time-to-live) boolean isExpired() { if (ttl == 0) { return false; } return lastAccess + ttl < System.currentTimeMillis(); } V2 getObject() { lastAccess = System.currentTimeMillis(); accessCount++; return cachedObject; } } protected Map<K,CacheObject<K,V>> cacheMap; private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(); private final Lock readLock = cacheLock.readLock(); private final Lock writeLock = cacheLock.writeLock(); protected int cacheSize; // 緩存大小 , 0 -> 無限制 protected boolean existCustomExpire ; //是否設置默認過時時間 public int getCacheSize() { return cacheSize; } protected long defaultExpire; // 默認過時時間, 0 -> 永不過時 public AbstractCacheMap(int cacheSize ,long defaultExpire){ this.cacheSize = cacheSize ; this.defaultExpire = defaultExpire ; } public long getDefaultExpire() { return defaultExpire; } protected boolean isNeedClearExpiredObject(){ return defaultExpire > 0 || existCustomExpire ; } public void put(K key, V value) { put(key, value, defaultExpire ); } public void put(K key, V value, long expire) { writeLock.lock(); try { CacheObject<K,V> co = new CacheObject<K,V>(key, value, expire); if (expire != 0) { existCustomExpire = true; } if (isFull()) { eliminate() ; } cacheMap.put(key, co); } finally { writeLock.unlock(); } } /** * {@inheritDoc} */ public V get(K key) { readLock.lock(); try { CacheObject<K,V> co = cacheMap.get(key); if (co == null) { return null; } if (co.isExpired() == true) { cacheMap.remove(key); return null; } return co.getObject(); } finally { readLock.unlock(); } } public final int eliminate() { writeLock.lock(); try { return eliminateCache(); } finally { writeLock.unlock(); } } /** * 淘汰對象具體實現 * * @return */ protected abstract int eliminateCache(); public boolean isFull() { if (cacheSize == 0) {//o -> 無限制 return false; } return cacheMap.size() >= cacheSize; } public void remove(K key) { writeLock.lock(); try { cacheMap.remove(key); } finally { writeLock.unlock(); } } public void clear() { writeLock.lock(); try { cacheMap.clear(); } finally { writeLock.unlock(); } } public int size() { return cacheMap.size(); } public boolean isEmpty() { return size() == 0; } }
LRU緩存實現類spa
import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; /** * LRU 實現 * @author Wen * * @param <K> * @param <V> */ public class LRUCache<K, V> extends AbstractCacheMap<K, V> { public LRUCache(int cacheSize, long defaultExpire) { super(cacheSize , defaultExpire) ; //linkedHash已經實現LRU算法 是經過雙向鏈表來實現,當某個位置被命中,經過調整鏈表的指向將該位置調整到頭位置,新加入的內容直接放在鏈表頭,如此一來,最近被命中的內容就向鏈表頭移動,須要替換時,鏈表最後的位置就是最近最少使用的位置 this.cacheMap = new LinkedHashMap<K, CacheObject<K, V>>( cacheSize +1 , 1f,true ) { @Override protected boolean removeEldestEntry( Map.Entry<K, CacheObject<K, V>> eldest) { return LRUCache.this.removeEldestEntry(eldest); } }; } private boolean removeEldestEntry(Map.Entry<K, CacheObject<K, V>> eldest) { if (cacheSize == 0) return false; return size() > cacheSize; } /** * 只須要實現清除過時對象就能夠了,linkedHashMap已經實現LRU */ @Override protected int eliminateCache() { if(!isNeedClearExpiredObject()){ return 0 ;} Iterator<CacheObject<K, V>> iterator = cacheMap.values().iterator(); int count = 0 ; while(iterator.hasNext()){ CacheObject<K, V> cacheObject = iterator.next(); if(cacheObject.isExpired() ){ iterator.remove(); count++ ; } } return count; } }
LFU實現類code
import java.util.HashMap; import java.util.Iterator; //LFU實現 public class LFUCache<K,V> extends AbstractCacheMap<K, V> { public LFUCache(int cacheSize, long defaultExpire) { super(cacheSize, defaultExpire); cacheMap = new HashMap<K, CacheObject<K,V>>(cacheSize+1) ; } /** * 實現刪除過時對象 和 刪除訪問次數最少的對象 * */ @Override protected int eliminateCache() { Iterator<CacheObject<K, V>> iterator = cacheMap.values().iterator(); int count = 0 ; long minAccessCount = Long.MAX_VALUE ; while(iterator.hasNext()){ CacheObject<K, V> cacheObject = iterator.next(); if(cacheObject.isExpired() ){ iterator.remove(); count++ ; continue ; }else{ minAccessCount = Math.min(cacheObject.accessCount , minAccessCount) ; } } if(count > 0 ) return count ; if(minAccessCount != Long.MAX_VALUE ){ iterator = cacheMap.values().iterator(); while(iterator.hasNext()){ CacheObject<K, V> cacheObject = iterator.next(); cacheObject.accessCount -= minAccessCount ; if(cacheObject.accessCount <= 0 ){ iterator.remove(); count++ ; } } } return count; } }
FIFO實現類對象
import java.util.Iterator; import java.util.LinkedHashMap; /** * FIFO實現 * @author Wen * * @param <K> * @param <V> */ public class FIFOCache<K, V> extends AbstractCacheMap<K, V> { public FIFOCache(int cacheSize, long defaultExpire) { super(cacheSize, defaultExpire); cacheMap = new LinkedHashMap<K, CacheObject<K, V>>(cacheSize + 1); } @Override protected int eliminateCache() { int count = 0; K firstKey = null; Iterator<CacheObject<K, V>> iterator = cacheMap.values().iterator(); while (iterator.hasNext()) { CacheObject<K, V> cacheObject = iterator.next(); if (cacheObject.isExpired()) { iterator.remove(); count++; } else { if (firstKey == null) firstKey = cacheObject.key; } } if (firstKey != null && isFull()) {//刪除過時對象仍是滿,繼續刪除鏈表第一個 cacheMap.remove(firstKey); } return count; } }