我们结合平常使用的Redis来想下,自己实现本地缓存需要考虑哪些因素呢,我这里总结了三点:
- 数据存储,基于Java实现的话我首先想到的是key-value结构的集合,如HashMap,并发环境下的话使用ConcurrentHashMap、随着访问顺序元素位置会变化的LinkedHashMap
- 缓存过期删除策略,参考Redis的定期删除和惰性删除
- 缓存淘汰策略,有先进先出、最少使用、最近最少使用(LRU)、随机等策略。
由以上可知,
- 要实现缓存过期删除的话,需要记录元素的生效时间,可以实现一个监控线程或用ScheduledExecutorService实现延迟删除。
- 缓存淘汰策略采用最近最少使用(LRU)的话,需要维护一个元素的使用队列
实现方式需要涉及的Java类有两种方式:
- ReentrantLock+ConcurrentHashMap + ConcurrentLinkedQueue
- ReentrantLock+ LinkedHashMap(本文以这种方式实现)
详细实现代码见下,
public class LocalCache {
//默认的缓存容量
private static int DEFAULT_CAPACITY = 16;
//最大容量
private static int MAX_CAPACITY = 64;
private ReentrantLock lock = new ReentrantLock();
private static Map<String, CacheData> linkedHashMap = new LinkedHashMap<String, CacheData>(DEFAULT_CAPACITY, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
//缓存淘汰
return MAX_CAPACITY < linkedHashMap.size();
}
};
private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
public void put(String key, String value, long expire) {
lock.lock();
try {
CacheData data = new CacheData();
data.setKey(key);
data.setValue(value);
data.setExpireTime(expire + System.currentTimeMillis());
linkedHashMap.put(key, data);
if (expire > 0) {
removeAfterExpireTime(key, expire);
}
} finally {
lock.unlock();
}
}
/**
* 过期删除
*/
public CacheData get(String key) {
lock.lock();
try {
return linkedHashMap.getOrDefault(key, new CacheData());
} finally {
lock.unlock();
}
}
private void removeAfterExpireTime(String key, long expireTime) {
scheduledExecutorService.schedule(() -> {
System.out.println("过期后清除该键值对");
linkedHashMap.remove(key);
}, expireTime, TimeUnit.MILLISECONDS);
}
private LocalCache () {
}
private static class CacheLocal {
private static LocalCache cache = new LocalCache ();
}
public static LocalCache getInstance() {
return CacheLocal.cache;
}
private class CacheData {
private String key;
private String value;
/**
* 存活时间
*/
private long expireTime;
private String getValue() {
return value;
}
private void setValue(String value) {
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public long getExpireTime() {
return expireTime;
}
public void setExpireTime(long expireTime) {
this.expireTime = expireTime;
}
}
}