Guava Cache : LoadingCache是一个本地缓存。
优点
- 线程安全的缓存,与ConcurrentMap相似,但前者增加了更多的元素失效策略,后者只能显示的移除元素。
- 提供了三种基本的缓存回收方式:基于
容量回收
、定时回收
和基于引用回收
。定时回收有两种:按照写入时间,最早写入的最先回收;按照访问时间,最早访问的最早回收。 - 监控缓存加载/命中情况。
- 集成了多部操作,调用get方式,可以在未命中缓存的时候,从其他地方获取数据源(DB,redis),并加载到缓存中。
缺点
超时机制不是精确的。
public static void main(String[] args) throws ExecutionException, InterruptedException{
//缓存接口这里是LoadingCache,LoadingCache在缓存项不存在时可以自动加载缓存
LoadingCache<Integer,Student> studentCache
//CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例
= CacheBuilder.newBuilder()
//设置并发级别为8,并发级别是指可以同时写缓存的线程数
.concurrencyLevel(8)
//设置写缓存后8秒钟过期
.expireAfterWrite(8, TimeUnit.SECONDS)
//设置缓存容器的初始容量为10
.initialCapacity(10)
//设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
.maximumSize(100)
//设置要统计缓存的命中率
.recordStats()
//设置缓存的移除通知
.removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(RemovalNotification<Object, Object> notification) {
System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());
}
})
//build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
.build(
new CacheLoader<Integer, Student>() {
@Override
public Student load(Integer key) throws Exception {
System.out.println("load student " + key);
Student student = new Student();
student.setId(key);
student.setName("name " + key);
return student;
}
}
);
for (int i=0;i<20;i++) {
//从缓存中得到数据,由于我们没有设置过缓存,所以需要通过CacheLoader加载缓存数据
Student student = studentCache.get(1);
System.out.println(student);
//休眠1秒
TimeUnit.SECONDS.sleep(1);
}
System.out.println("cache stats:");
//最后打印缓存的命中率等 情况
System.out.println(studentCache.stats().toString());
}
输出结果:
cache stats:
CacheStats{hitCount=17, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=1348802, evictionCount=2}
原因:看看到在20此循环中命中次数是17次,未命中3次,这是因为我们设定缓存的过期时间是写入后的8秒,所以20秒内会失效两次,另外第一次获取时缓存中也是没有值的,所以才会未命中3次,其他则命中。
- 不能持久化本地缓存
常用方法
- V getIfPresent(Object key) 获取缓存中key对应的value,如果缓存没命中,返回null。
- V get(K key) throws ExecutionException 获取key对应的value,若缓存中没有,则调用LocalCache的load方法,从数据源中加载,并缓存。
- void put(K key, V value) 如果缓存有值,覆盖,否则,新增
- void putAll(Map m);循环调用单个的方法
- void invalidate(Object key); 删除缓存
- void invalidateAll(); 清楚所有的缓存,相当远map的clear操作。
- long size(); 获取缓存中元素的大概个数。为什么是大概呢?元素失效之时,并不会实时的更新size,所以这里的size可能会包含失效元素。
- CacheStats stats(); 缓存的状态数据,包括(未)命中个数,加载成功/失败个数,总共加载时间,删除个数等。
- asMap()方法获得缓存数据的ConcurrentMap快照
- cleanUp()清空缓存
- refresh(Key) 刷新缓存,即重新取缓存数据,更新缓存
- ImmutableMap getAllPresent(Iterable keys) 一次获得多个键的缓存值
核心类
- CacheBuilder:类,缓存构建器。构建缓存的入口,指定缓存配置参数并初始化本地缓存。
- CacheBuilder在build方法中,会把前面设置的参数,全部传递给LocalCache,它自己实际不参与任何计算。这种初始化参数的方法值得借鉴,代* 码简洁易读。
- CacheLoader:抽象类。用于从数据源加载数据,定义load、reload、loadAll等操作。
- Cache:接口,定义get、put、invalidate等操作,这里只有缓存增删改的操作,没有数据加载的操作。
- AbstractCache:抽象类,实现Cache接口。其中批量操作都是循环执行单次行为,而单次行为都没有具体定义。
- LoadingCache:接口,继承自Cache。定义get、getUnchecked、getAll等操作,这些操作都会从数据源load数据。
- AbstractLoadingCache:抽象类,继承自AbstractCache,实现LoadingCache接口。
- LocalCache:类。整个guava cache的核心类,包含了guava cache的数据结构以及基本的缓存的操作方法。
- LocalManualCache:LocalCache内部静态类,实现Cache接口。
- 其内部的增删改缓存操作全部调用成员变量 localCache(LocalCache类型)的相应方法。
- LocalLoadingCache:LocalCache内部静态类,继承自LocalManualCache类,实现LoadingCache接口。
- 其所有操作也是调用成员变量localCache(LocalCache类型)的相应方法。
- CacheStats:缓存加载/命中统计信息。
个人工具类方法,仅供参考
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
public class CacheUtil {
private static final Logger logger = LoggerFactory.getLogger(CacheUtil.class);
private static final RedisUtil redisUtil = RedisUtil.getInstance();
/**
* load方式缓存
*
* @param key
* @param value
*/
public static void putByLoad(String key, Object value) {
String valStr = JSON.toJSONString(value);
logger.info("[CacheUtil] putByLoad, key -> " + key + ", value -> " + valStr);
CacheUtil.getInstanceForLoad().put(key, valStr);
redisUtil.set(key, valStr);
}
/**
* load方式读取单个bean
*
* @param key
* @param t
* @return
* @throws Exception
*/
public static <T> T getObjectByLoad(String key, Class<T> t) {
if (key == null)
return null;
String object = null;
try {
object = CacheUtil.getInstanceForLoad().get(key);
logger.info("[CacheUtil] get from cache, key -> " + key + ", value -> " + object);
} catch (Exception e) {
logger.error("[CacheUtil] getByLoad error -> " + e);
}
if (object == null)
return null;
return (T) JSON.parseObject(object, t);
}
/**
* load方式读取list
*
* @param key
* @param t
* @return
*/
public static <T> List<T> getListByLoad(String key, Class<T> t) {
if (key == null)
return null;
String object = null;
try {
object = CacheUtil.getInstanceForLoad().get(key);
} catch (Exception e) {
logger.error("[CacheUtil] getByLoad error -> " + e);
}
if (object == null)
return null;
return JSON.parseArray(object, t);
}
/**
* load方式读取map
*
* @param key
* @param t
* @return
*/
public static <T> Map<String, T> getMapByLoad(String key, Class<T> t) {
if (key == null)
return null;
String object = null;
try {
object = CacheUtil.getInstanceForLoad().get(key);
logger.info("[CacheUtil] get from cache, key -> " + key + ", value -> " + object);
} catch (Exception e) {
logger.error("[CacheUtil] getByLoad error -> " + e);
}
if (object == null)
return null;
Map<String, T> map = JSONObject.parseObject(object, new TypeReference<Map<String, T>>() {
});
for (String k : map.keySet()) {
T o = JSON.parseObject(map.get(k).toString(), t);
map.put(k, o);
}
return map;
}
/**
* map方式缓存
*
* @param key
* @param value
*/
public static void putByMap(String key, Object value) {
CacheUtil.getInstanceForMap().put(key, JSON.toJSONString(value));
}
/**
* map方式读取
*
* @param key
* @param t
* @return
*/
public static <T> T getByMap(String key, Class<T> t) {
Object object = CacheUtil.getInstanceForMap().getIfPresent(key);
if (object == null)
return null;
return (T) JSON.parseObject((String) object, t);
}
public static LoadingCache<String, String> getInstanceForLoad() {
return SingleTonForLoad.cahceBuilder;
}
private static class SingleTonForLoad {
private static LoadingCache<String, String> cahceBuilder = CacheBuilder.newBuilder()
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
redisUtil.expire(key, 0);
String re = redisUtil.get(key);
logger.info("[CacheUtil] get from redis, key -> " + key + ", value -> " + re);
return re;
}
});
}
public static Cache<String, Object> getInstanceForMap() {
return SingleTonForMap.cache;
}
private static class SingleTonForMap {
private static Cache<String, Object> cache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES)
.build();
}
public static void main(String[] args) {
Map<String, CompetitionBean> map1 = Maps.newHashMap();
CompetitionBean bean1 = new CompetitionBean();
bean1.setCategory("足球");
map1.put("1", bean1);
CacheUtil.putByLoad("a", map1);
System.err.println(JSON.toJSONString(CacheUtil.getMapByLoad("a", CompetitionBean.class)));
Map<String, CompetitionBean> map2 = Maps.newHashMap();
CompetitionBean bean2 = new CompetitionBean();
bean2.setCategory("篮球");
map2.put("2", bean2);
CacheUtil.putByLoad("a", map2);
System.err.println(JSON.toJSONString(CacheUtil.getMapByLoad("a", CompetitionBean.class)));
}
}