您的位置:首页 > 编程语言 > Java开发

简单的java缓存实现(LRU,LFU,FIFO)

2015-04-11 20:13 716 查看
原帖地址: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





/**

* 缓存接口

* @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();

}

基本实现抽象类

[java] view
plaincopy





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缓存实现类

[html] view
plaincopy





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实现类

[java] view
plaincopy





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实现类

[java] view
plaincopy





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;

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 java