簡單的java緩存實現

提到緩存,不得不提就是緩存算法(淘汰算法),常見算法有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();

}



基本實現抽象類



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緩存實現類



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實現類



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

}
相關文章
相關標籤/搜索