这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战。
Guava缓存是轻量级的,它将内容缓存到运行内存中。如果系统中某些值(比如一些配置表)被频繁查询使用,并且我们愿意消耗一些内存空间来提升应用的速度,减轻数据库压力的话,Guava缓存将会是一个不错的选择。由于缓存是存储在运行内存中的,所以我们需要确保缓存的大小不超出内存的容量。
创建缓存
我们可以直接创建Guava缓存对象,而不使用任何的CacheLoader:
Cache<String, String> cache = CacheBuilder.newBuilder().build();
cache.put("hello", "world");
System.out.println(cache.getIfPresent("hello")); // world
key值是大小写敏感的,所以使用cache.getIfPresent("HELLO")
将返回null值。
接下来看看如何使用CacheLoader创建缓存对象:
CacheLoader<String, String> loader = new CacheLoader<String, String>() {
@Override
public String load(String key) {
return sayHello(key);
}
};
LoadingCache<String, String> cache = CacheBuilder.newBuilder().build(loader);
String ird = cache.getUnchecked("ird");
System.out.println(ird); // hello ird
...
private String sayHello(String key) {
return String.format("hello %s", key);
}
方法getUnchecked
作用为:当值不存在时,会通过CacheLoader计算出值,然后存到缓存中。
驱逐机制
我们可以定义一些驱逐缓存的机制来限制缓存的大小。
限制缓存数目
我们可以通过maximumSize
来限制缓存的条目:
Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(3).build();
cache.put("k1", "v1");
cache.put("k2", "v2");
cache.put("k3", "v3");
cache.put("k4", "v4");
System.out.println(cache.size()); // 3
System.out.println(cache.getIfPresent("k1")); // null
System.out.println(cache.asMap()); // {k3=v3, k4=v4, k2=v2}
我们限制最多只能存储3个值,所以k4的存入把最早的k1给驱逐出去了,类似于FIFO。
限制缓存大小
我们可以自定义权重函数来限制缓存的大小:
Weigher<String, String> weigher = (key, value) -> value.length();
Cache<String, String> cache = CacheBuilder.newBuilder().maximumWeight(15).weigher(weigher).build();
cache.put("k1", "11111");
cache.put("k2", "4566");
cache.put("k3", "35673");
cache.put("k4", "636");
cache.put("k5", "555255");
System.out.println(cache.size()); // 3
System.out.println(cache.getIfPresent("k1")); // null
System.out.println(cache.asMap()); // {k3=35673, k5=555255, k4=636}
上面例子中,我们通过maximumWeight(15)
指定了缓存的最大容量,权重规则为value的长度。k3,k4和k5的value长度加起来为13,所以k1和k2的值存不下了,被驱逐。
设置缓存时间
我们可以设置缓存的有效时间和缓存的活跃时间。
设置缓存的活跃时间为2s:
Cache<String, String> cache = CacheBuilder.newBuilder().expireAfterAccess(2, TimeUnit.SECONDS).build();
cache.put("k1", "v1");
cache.put("k2", "v2");
cache.getIfPresent("k1");
TimeUnit.SECONDS.sleep(1);
cache.getIfPresent("k1");
TimeUnit.SECONDS.sleep(1);
System.out.println(cache.getIfPresent("k1")); // v1
System.out.println(cache.getIfPresent("k2")); // null
System.out.println(cache.size()); // 1
System.out.println(cache.asMap()); // {k1=v1}
上面代码中,我们通过cache.getIfPresent("k1")
获取了k1的值,然后让线程阻塞1秒,这时候k1和k2的有效时间大约为1秒左右。接着又获取了k1的值,所以k1的有效时间还是2秒,k2为1秒,再次让线程阻塞1秒后,k1的有效时间为1秒,k2已经失效了。打印输出的结果和我们预期的一致。
设置缓存的有效时间为2s:
Cache<String, String> cache = CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.SECONDS).build();
cache.put("k1", "v1");
cache.put("k2", "v2");
cache.getIfPresent("k1");
TimeUnit.SECONDS.sleep(1);
cache.getIfPresent("k1");
TimeUnit.SECONDS.sleep(1);
System.out.println(cache.getIfPresent("k1")); // null
System.out.println(cache.getIfPresent("k2")); // null
System.out.println(cache.size()); // 0
System.out.println(cache.asMap()); // {}
因为我们设置缓存有效时间为2秒,所以2秒后所有缓存都过期失效了,无论期间获取过多少次缓存。
weakKeys&softValues
默认情况下,Guava缓存键值都有强引用,我们可以使用weakKeys和softValues来让键值变为弱引用,这样垃圾收集器在必要的情况下将会工作:
Cache<String, String> cache = CacheBuilder.newBuilder().weakKeys().softValues().build();
刷新缓存
可以通过refreshAfterWrite
设置缓存自动刷新间隔,或者可以直接调用refresh
方法来手动刷新缓存:
Cache<String, String> cache = CacheBuilder.newBuilder().refreshAfterWrite(1,TimeUnit.SECONDS).build();
添加多个缓存
可以通过putAll
来一次性添加多个缓存:
Cache<String, String> cache = CacheBuilder.newBuilder().build();
Map<String, String> map = Maps.newHashMap();
map.put("k1", "v1");
map.put("k2", "v2");
map.put("k3", "v3");
cache.putAll(map);
System.out.println(cache.size()); // 3
System.out.println(cache.asMap()); // {k3=v3, k1=v1, k2=v2}
删除缓存
Cache.invalidate(key)
方法通过key来删除缓存:
Cache<String, String> cache = CacheBuilder.newBuilder().build();
Map<String, String> map = Maps.newHashMap();
map.put("k1", "v1");
cache.putAll(map);
System.out.println(cache.asMap()); // {k1=v1}
cache.invalidate("k1");
System.out.println(cache.asMap()); // {}
除此之外,我们也可以通过Cache.invalidateAll(keys)
一次性删除多个缓存或者Cache.invalidateAll()
删除全部缓存。
我们还可以给删除事件添加监听器:
RemovalListener<String, String> listener
= notification -> System.out.println("监听到删除事件,key=" + notification.getKey() + ",value=" + notification.getValue());
Cache<String, String> cache = CacheBuilder.newBuilder().removalListener(listener).build();
cache.put("k1", "v1");
cache.invalidate("k1"); // 监听到删除事件,key=k1,value=v1
增删改查
简单封装一个Guava缓存工具类:
public class GuavaCacheUtil {
private static Logger logger = LoggerFactory.getLogger(GuavaCacheUtil.class);
private static Cache<String, String> cache;
static {
RemovalListener<String, String> listener
= n -> logger.info("监听到删除事件,key={},value={}", n.getKey(), n.getValue());
cache = CacheBuilder.newBuilder()
.removalListener(listener).build();
}
/**
* 添加缓存
*
* @param key 键
* @param value 值
*/
public void put(String key, String value) {
if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
cache.put(key, value);
}
}
/**
* 批量添加缓存
*
* @param map key,value集合
*/
public void putAll(Map<String, String> map) {
cache.putAll(map);
}
/**
* 删除缓存
*
* @param key 键
*/
public void remove(String key) {
if (StringUtils.isNotBlank(key)) {
cache.invalidate(key);
}
}
/**
* 批量删除缓存
*
* @param keys key集合
*/
public void remove(List<String> keys) {
if (CollectionUtils.isNotEmpty(keys)) {
cache.invalidateAll(keys);
}
}
/**
* 清空缓存
*/
public void removeAll() {
cache.invalidateAll();
}
/**
* 获取缓存
*
* @param key 键
* @return 值
*/
public String get(String key) {
return StringUtils.isNotBlank(key) ? cache.getIfPresent(key) : null;
}
/**
* 批量获取缓存
*
* @param keys 键集合
* @return 值集合
*/
public ImmutableMap<String, String> get(List<String> keys) {
return CollectionUtils.isNotEmpty(keys) ? cache.getAllPresent(keys) : null;
}
}