Google Guava Cache是一种非常优秀本地缓存解决方案,提供了基于容量,时间和引用的缓存回收方式。
- 基于容量的方式内部实现采用LRU算法,
- 基于引用回收很好的利用了Java 虚拟机的垃圾回收机制。
Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。Guava Cache为了限制内存占用,通常都设定为自动回收元素。
maven 引用
<dependencies>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
</dependencies>
适用场景
- 愿意消耗一些内存空间来提升速度
- 预料到某些键会被多次查询
- 缓存中存放的数据总量不会超出内存总量
Tips: guava cache是运行在JVM的本地缓存,并不能把数据存放到外部服务器上。如果有这样的要求,因该尝试Memcached或 Redis这类分布式缓存。
Guava Cache 加载
-
加载方式1 - CacheLoader
LoadingCache是附带CacheLoader构建而成的缓存实现。创建自己的CacheLoader通常只需要简单地实现V load(K key) throws Exception方法。
package cn.lazyfennec.guava;
import com.google.common.cache.*;
import java.util.concurrent.TimeUnit;
/**
* 加载方式1:CacheLoader
* 1.设置缓存容量
* 2.设置超时时间
* 3.提供移除监听器
* 4.提供缓存加载器
* 5.构建缓存
*
* @Author: Neco
* @Description: 加载方式1:CacheLoader
* @Date: create in 2022/6/9 16:22
*/
public class GuavaCacheDemo1 {
public static void main(String[] args) {
// CacheLoader 初始化
CacheLoader<String, Object> cacheLoader = new CacheLoader<String, Object>() {
@Override
// load方法的作用是在通过get方法从LoadingCache获取不到值时去加载该值并放入缓存。
public String load(String key) throws Exception {
// 模拟加载值的过程
Thread.sleep(1000);
// 假设某个key是非法访问的
if ("err".equals(key)) {
return null;
}
return key + "'s value";
}
};
// 移出监听器配置
RemovalListener<String, Object> removalListener = new RemovalListener<String, Object>() {
// 移出时触发的事件
public void onRemoval(RemovalNotification<String, Object> removal) {
System.out.println("[" + removal.getKey() + ":" + removal.getValue() + "] is evicted!");
}
};
//
LoadingCache<String, Object> caches = CacheBuilder.newBuilder()
// 设置容量大小
.maximumSize(5)
// 设置超时时间
.expireAfterWrite(10, TimeUnit.SECONDS)
.expireAfterAccess(10, TimeUnit.SECONDS)
// 移出监听器
.removalListener(removalListener)
// 加载器配置
.build(cacheLoader);
// 由于缓存的容易只设置了5个,存入10个就会由guava基于容量回收掉5个
for (int i = 0; i < 10; i++) {
String key = "key" + i;
String value = "value" + i;
caches.put(key, value);
System.out.println("[" + key + ":" + value + "] is put into cache!");
}
// 如果存在就获取
System.out.println(caches.getIfPresent("key6"));
try {
caches.get("err");
} catch (Exception e) {
// e.printStackTrace();
System.out.println("不存在key,会报错");
}
}
}
- 运行结果
[key0:value0] is put into cache!
[key1:value1] is put into cache!
[key2:value2] is put into cache!
[key3:value3] is put into cache!
[key4:value4] is put into cache!
[key0:value0] is evicted!
[key5:value5] is put into cache!
[key1:value1] is evicted!
[key6:value6] is put into cache!
[key2:value2] is evicted!
[key7:value7] is put into cache!
[key3:value3] is evicted!
[key8:value8] is put into cache!
[key4:value4] is evicted!
[key9:value9] is put into cache!
value6
不存在key,会报错
-
加载方式2 - Callable
所有类型的Guava Cache,不管有没有自动加载功能,都支持get(K,Callable<V>)方法。这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。在整个加载方法完成前,缓存项相关的可观察状态都不会更改。这个方法简便地实现了模式"如果有缓存则返回;否则运算、缓存、然后返回"。
package cn.lazyfennec.guava;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
/**
* 加载方式2:Callable
* 所有类型的Guava Cache,不管有没有自动加载功能,都支持get(K, Callable<V>)方法。
* 这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。
* 在整个加载方法完成前,缓存项相关的可观察状态都不会更改。
* 这个方法简便地实现了模式"如果有缓存则返回;否则运算、缓存、然后返回"。
*
* @Author: Neco
* @Description: 加载方式2:Callable
* @Date: create in 2022/6/9 17:04
*/
public class GuavaCacheDemo2 {
// 构建容量为3的缓存对象
static Cache<String, String> caches = CacheBuilder.newBuilder()
.maximumSize(3)
.build();
public static void main(String[] args) {
caches.put("1234", "我是存在的");
// 如果存在就获取,不存在返回null
System.out.println(caches.getIfPresent("key6"));
try {
// 获取key为123的缓存数据,如果有就返回,没有就返回call方法的返回值
System.out.println(caches.get("123", new Callable<String>() {
@Override
public String call() throws Exception {
return "运算、缓存、然后返回";
}
}));
// 获取key为1234的缓存数据,如果有就返回,没有就返回call方法的返回值。注意这里key是存在的
System.out.println(caches.get("1234", new Callable<String>() {
@Override
public String call() throws Exception {
return "我是打酱油的";
}
}));
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
- 运行结果
null
运算、缓存、然后返回
我是存在的
Guaca Cache 缓存回收
- 回收方式1 - 基于容量回收
maximumSize(long):当缓存中的元素数量超过指定值时。
// 设置缓存容量
.maximumSize(5)
-
回收方式2 - 定时回收
expireAfterAccess(long,TimeUnit)和 expireAfterAccess(long,TimeUnit) :缓存项在给定时间内没有被读/写访问,则回收。
// 设置超时时间
.expireAfterWrite(10, TimeUnit.MINUTES)
.expireAfterAccess(10, TimeUnit.MINUTES)
-
回收方式3 - 基于引用回收 (Reference-based Eviction)
CacheBuilder.weakKeys():使用弱引用存储键。当key没有其它引用时,缓存项可以被垃圾回收。CacheBuilder.weakValues():使用弱引用存储值。当value没有其它引用时,缓存项可以被垃圾回收。CacheBuilder.softValues():使用软引用存储值,按照全局最近最少使用的顺序回收。
// 当值没有其它(强或软)引用时,缓存项可以被垃圾回收
.weakValues()
Guaca Cache 显式清除
任何时候都可以显式地清除缓存项,而不是等待被回收:
- 个别清除: Cache.invalidate(key)
- 批量清除:Cache.invalidateAll(keys)
- 清除所有缓存项:Cache.invalidateAll()
package cn.lazyfennec.guava;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
/**
* @Author: Neco
* @Description:
* @Date: create in 2022/6/9 17:14
*/
public class GuavaCacheDemo3 {
static Cache<String, Object> testCache = CacheBuilder.newBuilder()
// 当值没有其它(强或软)引用时,缓存项可以被垃圾回收
.weakValues()
// 开启Guava Cache的统计功能
.recordStats()
.build();
public static void main(String[] args) {
Object obj1 = new Object();
testCache.put("1234", obj1);
obj1 = new String("123");
// 主动gc
System.gc();
System.out.println(testCache.getIfPresent("1234"));
/*
stats()方法会返回CacheS tats 对象以提供如下统计信息:
hitRate():缓存命中率;
averageLoadPenalty():加载新值的平均时间,单位为纳秒;
evictionCount():缓存项被回收的总数,不包括显式清除。
*/
System.out.println(testCache.stats());
}
}
- 运行结果
null
CacheStats{hitCount=0, missCount=1, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=0}
Guava Cache 统计
- 开启Guava Cache 的统计功能 :CacheBuilder.recordStats()
- Cache.stats() 会返回CacheStats对象以提供以下统计信息:
- hitRate():缓存命中率
- averageLoadPenalty():加载新值的平均时间,单位为纳秒
- evictionCount():缓存项被回收的总数,不包括显示清除
- 例子见上方demo3
如果觉得有收获就点个赞吧,更多知识,请点击关注查看我的主页信息哦~
网友评论