博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Guava系列之Cache
阅读量:4186 次
发布时间:2019-05-26

本文共 8273 字,大约阅读时间需要 27 分钟。

Guava Cache的使用场景:

  • 以空间换取时间,就是你愿意用内存的消耗来换取读取性能的提升
  • 你已经预测到某些数据会被频繁的查询
  • 缓存中存放的数据不会超过内存空间

示例代码

@Test    public void cacheCreateTest(){        Cache
cache = CacheBuilder.newBuilder() .maximumSize(100) //设置缓存最大容量 .expireAfterWrite(1,TimeUnit.MINUTES) //过期策略,写入一分钟后过期 .build(); cache.put("a","a1"); String value = cache.getIfPresent("a"); }

Cache

Cache是Guava提供的最基本缓存接口,创建一个Cache很简单

Cache是通过CacheBuilder对象来build出来的,build之前可以设置一系列的参数

LoadingCache

LoadingCache继承自Cache,当从缓存中读取某个key时,假如没有读取到值,LoadingCache会自动进行加载数据到缓存

@Test    public void loadingCacheTest() throws ExecutionException {        LoadingCache
loadingCache = CacheBuilder.newBuilder() .maximumSize(3) .refreshAfterWrite(Duration.ofMillis(10))//10分钟后刷新缓存的数据 .build(new CacheLoader
() { @Override public String load(String key) throws Exception { Thread.sleep(1000); System.out.println(key + " load data"); return key + " add value"; } }); System.out.println(loadingCache.get("a")); System.out.println(loadingCache.get("b")); }

运行结果:

a load dataa add valueb load datab add value

LoadingCache也是通过CacheBuilder创建出来的,只不过创建的时候,需要在build方法里面传入CacheLoader

CacheLoader类的load方法就是在key找不到的情况下,进行数据自动加载的

下面我们看一下Guava Cache在使用时常用的属性,对Cache和LoadingCache都适用

1、容量初始化

public void initialCapacityTest(){        Cache
cache = CacheBuilder.newBuilder() .initialCapacity(1024) //初始容量 .build(); }

2、最大容量

最大容量可以通过两种维度来设置

  • maximumSize 最大记录数,存储数据的个数
  • maximumWeight 最大容量,存储数据的大小
@Test    public void maxSizeTest(){        Cache
cache = CacheBuilder.newBuilder() .maximumSize(2)//缓存最大个数 .build(); cache.put("a","1"); cache.put("b","2"); cache.put("c","3"); System.out.println(cache.getIfPresent("a")); System.out.println(cache.getIfPresent("b")); System.out.println(cache.getIfPresent("c")); Cache
cache1 = CacheBuilder.newBuilder() .maximumWeight(1024 * 1024 * 1024)//最大容量为1M //用来计算容量的Weigher .weigher(new Weigher
() { @Override public int weigh(String key, String value) { return key.getBytes().length + value.getBytes().length; } }) .build(); cache1.put("x","1"); cache1.put("y","2"); cache1.put("z","3"); System.out.println(cache1.getIfPresent("x")); System.out.println(cache1.getIfPresent("y")); System.out.println(cache1.getIfPresent("z")); }

运行结果:

null23123

我们设置缓存的最大记录为2,当我们添加三个元素进去后,会把前面添加的元素覆盖

3、过期时间

  • expireAfterWrite 写入后多长时间,数据就过期了
  • expireAfterAccess 数据多长时间没有被访问,就过期
@Test    public void expireTest() throws InterruptedException {        Cache
cache = CacheBuilder.newBuilder() .maximumSize(100)//缓存最大个数 .expireAfterWrite(5,TimeUnit.SECONDS)//写入后5秒过期 .build(); cache.put("a","1"); int i = 1; while(true){ System.out.println("第" + i + "秒获取到的数据为:" + cache.getIfPresent("a")); i++; Thread.sleep(1000); } }

运行结果

第1秒获取到的数据为:1第2秒获取到的数据为:1第3秒获取到的数据为:1第4秒获取到的数据为:1第5秒获取到的数据为:1第6秒获取到的数据为:null第7秒获取到的数据为:null第8秒获取到的数据为:null第9秒获取到的数据为:null
  • 从运行结果可以看出来,写入数据后的第6秒就开始获取不到数据了
@Test    public void expireAfterAccessTest() throws InterruptedException {        Cache
cache = CacheBuilder.newBuilder() .maximumSize(100)//缓存最大个数 .expireAfterAccess(5,TimeUnit.SECONDS)//5秒没有被访问,就过期 .build(); cache.put("a","1"); Thread.sleep(3000); System.out.println("休眠3秒后访问:" + cache.getIfPresent("a")); Thread.sleep(4000); System.out.println("休眠4秒后访问:" + cache.getIfPresent("a")); Thread.sleep(5000); System.out.println("休眠5秒后访问:" + cache.getIfPresent("a")); }

运行结果:

休眠3秒后访问:1休眠4秒后访问:1休眠5秒后访问:null

从运行结果可以看出,只要超过了设定的时间没有人访问,缓存的数据就会过期

回收策略

在上面我们讲了两种回收策略

  • expireAfterWrite 写入多长时间后就回收
  • expireAfterAccess 多长时间没有被访问就回收

 

Guava Cache还支持基于引用级别的回收,这种回收策略是Java特有的,在Java的对象回收机制中,按对象的强弱可以分为强引用、软引用、弱引用、虚引用

强引用

强引用是我们最常用的引用,比如我们直接new一个对象,就是一个强引用

Map
map = new HashMap
();

强引用不会被自动回收,当内存不足时直接报内存溢出

软引用

软引用是一种不稳定的引用方式,如果一个对象具有软引用,当内存充足时,GC不会主动回收软引用对象,而当内存不足时软引用对象就会被回收

SoftReference softRef=new SoftReference(new Object()); // 软引用Object object = softRef.get(); // 获取软引用

弱引用

弱引用是一种比软引用更不稳定的引用方式,因为无论内存是否充足,弱引用对象都有可能被回收

WeakReference weakRef = new WeakReference(new Object()); Object obj = weakRef.get(); // 获取弱引用

虚引用

虚引用这种引用方式就是形同虚设,因为如果一个对象仅持有虚引用,那么它就和没有任何引用一样。在实践中也几乎没有使用

在Guava中支持软/弱引用的回收方式

Cache
cache = CacheBuilder.newBuilder() .weakKeys() //使用弱引用存储键。当键没有其它(强或软)引用时,该缓存可能会被回收。 .weakValues() //使用弱引用存储值。当值没有其它(强或软)引用时,该缓存可能会被回收。 .softValues() //使用软引用存储值。当内存不足并且该值其它强引用引用时,该缓存就会被回收 .build();

手动回收

上面讲的都是缓存自动回收的策略,我们也可以调用Guava Cache提供的方法来手动清除

可以删除单个key,也可以批量删除key,同时也可以清除整个缓存的数据(谨慎使用哦~)

@Test    public void invalidateTest(){        Cache
cache = CacheBuilder.newBuilder().build(); cache.put("a","1"); cache.put("b","2"); //从缓存中清除key为a的数据 cache.invalidate("a"); System.out.println(cache.getIfPresent("a")); cache.put("x","x1"); cache.put("y","y1"); System.out.println("x清除之前:"+ cache.getIfPresent("x")); System.out.println("y清除之前:"+ cache.getIfPresent("y")); List
list = Lists.newArrayList("x","y"); //批量清除 cache.invalidateAll(list); System.out.println("x清除之后:"+ cache.getIfPresent("x")); System.out.println("y清除之后:"+ cache.getIfPresent("y")); cache.put("y","y1"); cache.put("z","z1"); //清空缓存所有的数据 cache.invalidateAll(); System.out.println("全部清除后:" + cache.getIfPresent("y")); System.out.println("全部清除后:" + cache.getIfPresent("z")); }

运行结果:

nullx清除之前:x1y清除之前:y1x清除之后:nully清除之后:null全部清除后:null全部清除后:null

数据清除监听器

可以给Cache中的对象加一个监听,当有对象被删除时会有事件通知

@Test    public void removeListenerTest() throws InterruptedException {        Cache
cache = CacheBuilder.newBuilder() .maximumSize(3) .expireAfterWrite(Duration.ofSeconds(5))//5秒后自动过期 //添加一个remove的监听器 .removalListener(new RemovalListener
() { @Override public void onRemoval(RemovalNotification
notification) { System.out.println("[" +notification.getKey() + ":" + notification.getValue() + "] 被删除了"); } }) .build(); cache.put("a","1"); Thread.sleep(2000); cache.put("b","2"); cache.put("c","3"); Thread.sleep(2000); cache.put("d","4"); Thread.sleep(5000); cache.put("e","5"); cache.invalidate("e"); }

运行结果:

[a:1] 被删除了[b:2] 被删除了[c:3] 被删除了[d:4] 被删除了

创建Cache时removalListener方法传入一个RemovalListener对象,重写onRemoval方法来进行数据清除事件的监听

从运行结果可以看出来,三种情况下的清除数据都会被监听

  • 超过容量,清除最早添加进缓存的数据
  • 超过设定的过期时间,缓存系统自动删除
  • 手动清除数据

统计信息

我们在使用缓存的时候,应该要关心缓存的命中率、加载数据时间等等信息,可以根据这些统计信息来对缓存进行优化调整,让缓存更好的为我们服务。在构建缓存对象时,可以开启统计信息,开启后会对缓存的操作进行统计

@Test    public void recordStatsTest(){        Cache
cache = CacheBuilder.newBuilder() .maximumSize(3) .recordStats() .build(); cache.put("a","1"); cache.put("b","2"); cache.put("c","3"); cache.put("d","4"); cache.put("e","5"); cache.put("f","6"); cache.getIfPresent("a"); cache.getIfPresent("a"); cache.getIfPresent("e"); cache.getIfPresent("f"); cache.getIfPresent("h"); cache.getIfPresent("t"); System.out.println(cache.stats()); }

运行结果:

CacheStats{hitCount=2, missCount=4, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=3}

参考:

https://www.cnblogs.com/fnlingnzb-learner/p/11022152.html

https://blog.csdn.net/l1028386804/article/details/102764951

你可能感兴趣的文章