cache 发展之路
1、HashMap或者ConcurrentHashMap
public class CustomerService {
private HashMap<String,String> hashMap = new HashMap<>();
private CustomerMapper customerMapper;
public String getCustomer(String name){
String customer = hashMap.get(name);
if ( customer == null){
customer = customerMapper.get(name);
hashMap.put(name,customer);
}
return customer;
}
}
但是这样做就有个问题HashMap无法进行数据淘汰,内存会无限制的增长,所以hashMap很快也被淘汰了。
2、LRUHashMap
我们可以通过继承 LinkedHashMap,重写 removeEldestEntry 方法,即可完成一个简单的 LRUMap。
class LRUMap extends LinkedHashMap {
private final int max;
private Object lock;
public LRUMap(int max, Object lock) {
//无需扩容
super((int) (max * 1.4f), 0.75f, true);
this.max = max;
this.lock = lock;
}
/**
* 重写LinkedHashMap的removeEldestEntry方法即可
* 在Put的时候判断,如果为true,就会删除最老的
* @param eldest
* @return
*/
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > max;
}
public Object getValue(Object key) {
synchronized (lock) {
return get(key);
}
}
public void putValue(Object key, Object value) {
synchronized (lock) {
put(key, value);
}
}
public boolean removeValue(Object key) {
synchronized (lock) {
return remove(key) != null;
}
}
public boolean removeAll(){
clear();
return true;
}
}
3、Guava cache
LRUMap,用来进行缓存数据的淘汰,但是有几个问题:
-
锁竞争严重,可以看见我的代码中,Lock是全局锁,在方法级别上面的,当调用量较大时,性能必然会比较低。
-
不支持过期时间
-
不支持自动刷新
public static void main(String[] args) throws ExecutionException {
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
//写之后30ms过期
.expireAfterWrite(30L, TimeUnit.MILLISECONDS)
//访问之后30ms过期
.expireAfterAccess(30L, TimeUnit.MILLISECONDS)
//20ms之后刷新
.refreshAfterWrite(20L, TimeUnit.MILLISECONDS)
//开启weakKey key 当启动垃圾回收时,该缓存也被回收
.weakKeys()
.build(createCacheLoader());
System.out.println(cache.get("hello"));
cache.put("hello1", "我是hello1");
System.out.println(cache.get("hello1"));
cache.put("hello1", "我是hello2");
System.out.println(cache.get("hello1"));
}public static com.google.common.cache.CacheLoader<String, String> createCacheLoader() {
return new com.google.common.cache.CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return key;
}
};
}
本文,我们来看一下,如何实现一个固定大小的缓存。
代码实现
1、接口定义
/**
* 缓存接口
* @author binbin.hou
* @since 0.0.1
*/
public interface ICache<K, V> extends Map<K, V> {
}
2、核心实现
@Override
public V put(K key, V value) {
//1.1 尝试驱除
CacheEvictContext<K,V> context = new CacheEvictContext<>();
context.key(key).size(sizeLimit).cache(this);
cacheEvict.evict(context);
//2. 判断驱除后的信息
if(isSizeLimit()) {
throw new CacheRuntimeException("当前队列已满,数据添加失败!");
}
//3. 执行添加
return map.put(key, value);
}
这里我们可以让用户动态指定大小,但是指定大小肯就要有对应的淘汰策略。
否则,固定大小的 map 肯定无法放入元素。
3、淘汰策略
public class CacheEvictFIFO<K,V> implements ICacheEvict<K,V> {
/**
* queue 信息
* @since 0.0.2
*/
private Queue<K> queue = new LinkedList<>();
@Override
public void evict(ICacheEvictContext<K, V> context) {
final ICache<K,V> cache = context.cache();
// 超过限制,执行移除
if(cache.size() >= context.size()) {
K evictKey = queue.remove();
// 移除最开始的元素
cache.remove(evictKey);
}
// 将新加的元素放入队尾
final K key = context.key();
queue.add(key);
}
}
FIFO 比较简单,我们使用一个队列,存储每一次放入的元素,当队列超过最大限制时,删除最早的元素。
4、引导类
/**
* 缓存引导类
* @author binbin.hou
* @since 0.0.2
*/
public final class CacheBs<K,V> {
private CacheBs(){}
/**
* 创建对象实例
* @param <K> key
* @param <V> value
* @return this
* @since 0.0.2
*/
public static <K,V> CacheBs<K,V> newInstance() {
return new CacheBs<>();
}
/**
* map 实现
* @since 0.0.2
*/
private Map<K,V> map = new HashMap<>();
/**
* 大小限制
* @since 0.0.2
*/
private int size = Integer.MAX_VALUE;
/**
* 驱除策略
* @since 0.0.2
*/
private ICacheEvict<K,V> evict = CacheEvicts.fifo();
/**
* map 实现
* @param map map
* @return this
* @since 0.0.2
*/
public CacheBs<K, V> map(Map<K, V> map) {
ArgUtil.notNull(map, "map");
this.map = map;
return this;
}
/**
* 设置 size 信息
* @param size size
* @return this
* @since 0.0.2
*/
public CacheBs<K, V> size(int size) {
ArgUtil.notNegative(size, "size");
this.size = size;
return this;
}
/**
* 设置驱除策略
* @param evict 驱除策略
* @return this
* @since 0.0.2
*/
public CacheBs<K, V> evict(ICacheEvict<K, V> evict) {
this.evict = evict;
return this;
}
/**
* 构建缓存信息
* @return 缓存信息
* @since 0.0.2
*/
public ICache<K,V> build() {
CacheContext<K,V> context = new CacheContext<>();
context.cacheEvict(evict);
context.map(map);
context.size(size);
return new Cache<>(context);
}
}
5、测试使用
ICache<String, String> cache = CacheBs.<String,String>newInstance()
.size(2)
.build();
cache.put("1", "1");
cache.put("2", "2");
cache.put("3", "3");
cache.put("4", "4");
Assert.assertEquals(2, cache.size());
System.out.println(cache.keySet());
[3, 4]