Guava Cache
一、Guava Cache 理解
开发中我们对一些访问非常频繁的数据,数据量可控,为了减少网络和硬盘io消耗,减少时间成本的消耗,我们通常会选择使用缓存来提高程序访问数据的性能,减少网络IO开销。
缓存大体分为2种:
(1)、集中式缓存。
(2)、分布式缓存。
而Guava就是集中式线程安全的本地缓存。Guava Cache简单的说是一个类似ConcurrentMap<K,V>缓存数据的集合,他有自己的缓存处理策略,自动回收,自动清除,自动加载等。
Guava缓存是单个应用中的本地缓存。它不会将数据存储到文件中,或者外部服务器;
二、Guava的使用
1、创建方式
(1)、Callable
(2)、LoadingCache
2、两种创建方式比较:
相同点:两种方式同样按照获取缓存-如果没有-则计算(get-if-absent-compute)的缓存规则对缓存数据进行的处理的。
不同点:在缓存中没有得到value的时候,CacheLoader会定义一个比较宽泛的、统一的根据key值load value的方法,而Callablee的方式较为灵活,允许你在get的时候指定call。
3、缓存的回收策略:基于容量回收、定时回收和基于引用回收
(1)、maximumSize(long):设置容量大小,超过就开始回收。
如果要规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)。缓存将尝试回收最近没有使用或总体上很少使用的缓存项;不同的缓存项有不同的“权重”(weights);
(2)、expireAfterWrite(long, TimeUnit):在这个时间段内没有被读/写访问,就会被回收。
CacheBuilder提供两种定时回收的方法: expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请 注意这种缓存的回收顺序和基于大小回收一样。 expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。
(3)、expireAfterAccess(long, TimeUnit):在这个时间段内没有被写访问,就会被回收;
通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:
CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅 依赖恒等式(==),使用弱引用键的缓存用==而不是equals比较键。
CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅 依赖恒等式(==),使用弱引用值的缓存用==而不是equals比较值。
CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存 同样用==而不是equals比较值。
4、常用API使用
LoadingCache:
(1)、get(K):使用这个方法要么返回已经缓存的值,要么使用CacheLoader向缓存原子地加载新值。由于CacheLoader可能抛出异常,LoadingCache.get(K)也声明为抛出ExecutionException异常。
如果你定义的CacheLoader没有声明任何检查型异常,则可以通过 getUnchecked(K) 查找缓存;但必须注意,一旦CacheLoader声明了检查型异常,就不可以调用getUnchecked(K)。
(2)、getIfPresent(key):从现有的缓存中获取,如果缓存中有key,则返回value,如果没有则返回null
(3)、getAll(Iterable<? extends K>)方法用来执行批量查询。默认情况下,对每个不在缓存中的键,getAll方法会单独调用CacheLoader.load来加载缓存项。可以通过重写load()方法来提高加载缓存的效率;
Callable:
(1)、get(K, Callable<V>):这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。实现了模式"如果有缓存则返回;否则运算、缓存、然后返回"。
*******put等等其他的就不一一去介绍了。。。具体使用看我贴出来的代码
----------------
公共(列举一部分)
(1)、Cache.invalidate(key) :个别清除
(2)、Cache.invalidateAll(keys):批量清除
(3)、Cache.invalidateAll():清除所有缓存项
(4)、removalListener(RemovalListener监听事件,在元素被删除,回收时,进行监听
(5)、CacheBuilder.recordStats()用来开启Guava Cache的统计功能。统计打开后,Cache.stats()方法会返回CacheStats对 象以提供如下统计信息:
hitRate():缓存命中率;
averageLoadPenalty():加载新值的平均时间,单位为纳秒;
evictionCount():缓存项被回收的总数,不包括显式清除。
代码测试案例
package cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Description:
* User: ZhuRong
* Date: 2018-08-10 9:34
*/
public class LoadingCacheTest {
private static LoadingCache<Integer, String> cacheMap = CacheBuilder.newBuilder()
.maximumSize(3) //缓存大小
.expireAfterAccess(5,TimeUnit.SECONDS) //加载缓存之后,多久过期
// .expireAfterWrite(1115,TimeUnit.SECONDS)//缓存被写入,update之后,多久过期
.recordStats().build(new CacheLoader<Integer, String>() {
//当本地缓存命没有中时,调用load方法获取结果并将结果缓存
public String load(Integer key) throws Exception {
//"缓存没了,请去数据库查找";
System.out.println("load():key = "+key);
if(cacheMap.getIfPresent(key) == null){
return getDbResultInfo(key);
}else{
return cacheMap.getIfPresent(key);
}
}
private String getDbResultInfo(int key) throws Exception {
System.out.println("正在查询...");
cacheMap.put(key,"这是数据库查询的结果");
return cacheMap.getIfPresent(key);
}
});
public static void main(String[] args) throws ExecutionException {
//超过上限的时候,后面覆盖前面的key
System.out.println("cacheMap size:"+cacheMap.size());
cacheMap.put(1,"a");
cacheMap.put(2,"b");
cacheMap.put(3,"c");
cacheMap.put(4,"d");
System.out.println("cacheMap size:"+cacheMap.size());
//缓存中如果有这个key返回value,如果没有这个key直接返回null
System.out.println("cacheMap getIfPresent:"+cacheMap.getIfPresent(1));
System.out.println("cacheMap getIfPresent:"+cacheMap.getIfPresent(2));
System.out.println("cacheMap getIfPresent:"+cacheMap.getIfPresent(3));
System.out.println("cacheMap getIfPresent:"+cacheMap.getIfPresent(4));
System.out.println("--------------------------------------");
try {
System.out.println("第一次查询:"+cacheMap.get(1));
System.out.println("第二次查询:"+cacheMap.get(1));
System.out.println("第三次查询:"+cacheMap.get(1)); //这个时候key=2被key=1覆盖
System.out.println("cacheMap size:"+cacheMap.size());
System.out.println("--------------------------------------");
System.out.println("第一次查询:"+cacheMap.get(3));
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("cacheMap getIfPresent:"+cacheMap.getIfPresent(5));
System.out.println("cacheMap get:"+cacheMap.get(5));
System.out.println("cacheMap get:"+cacheMap.get(5));
System.out.println("---------------后面是过期了的数据打印-------------------------------");
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//目前其实是key =3,4,5 过期
System.out.println("cacheMap size:"+cacheMap.size());
// System.out.println(cacheMap.getIfPresent(4)); //说明过期的缓存是没有清楚的,而是被标识无法读取
System.out.println(cacheMap.getIfPresent(6));
System.out.println("cacheMap size:"+cacheMap.size());
System.out.println(cacheMap.getIfPresent(5));
System.out.println("cacheMap size:"+cacheMap.size());
System.out.println(cacheMap.getIfPresent(6));
System.out.println("cacheMap size:"+cacheMap.size());
cacheMap.put(1,"aaaa");
System.out.println("cacheMap size:"+cacheMap.size());
}
}
打印结果:
看到结果是不是想到了什么?在什么情况下缓存会被清空?过期了缓存真的被清空了吗?缓存满了,后面继续put进去又会发生什么情况
仔细对着代码和输出结果你就恍然大悟了!哈哈,我就不说了,自己去运行研究,更加深理解!
package cache;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
/**
*
*
* Description:
* User: ZhuRong
* Date: 2018-08-10 11:06
*/
public class CallCacheTest {
public static Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(1000).build();
public static Object get(String key){
String result = null;
try {
result = cache.get(key, new Callable<String>() {
public String call() {
System.out.println("call() key");
return "result";
}
});
} catch (ExecutionException e) {
e.printStackTrace();
}
return result;
}
public static void main(String[] args) {
cache.put("key","value");
System.out.println(get("key"));
System.out.println(cache.getIfPresent("key"));
System.out.println(get("key1"));
}
}
结果输出: