Guava 缓存

Guava缓存是轻量级的,它将内容缓存到运行内存中。如果系统中某些值(比如一些配置表)被频繁查询使用,并且我们愿意消耗一些内存空间来提升应用的速度,减轻数据库压力的话,Guava缓存将会是一个不错的选择。由于缓存是存储在运行内存中的,所以我们需要确保缓存的大小不超出内存的容量。

创建缓存

我们可以直接创建Guava缓存对象,而不使用任何的CacheLoader:

1
2
3
4
Cache<String, String> cache = CacheBuilder.newBuilder().build();

cache.put("hello", "world");
System.out.println(cache.getIfPresent("hello")); // world

key值是大小写敏感的,所以使用cache.getIfPresent("HELLO")将返回null值。

接下来看看如何使用CacheLoader创建缓存对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 mrbird = cache.getUnchecked("mrbird");
System.out.println(mrbird); // hello mrbird

...
private String sayHello(String key) {
return String.format("hello %s", key);
}

方法getUnchecked作用为:当值不存在时,会通过CacheLoader计算出值,然后存到缓存中。

驱逐机制

我们可以定义一些驱逐缓存的机制来限制缓存的大小。

限制缓存数目

我们可以通过maximumSize来限制缓存的条目:

1
2
3
4
5
6
7
8
9
10
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。

限制缓存大小

我们可以自定义权重函数来限制缓存的大小:

1
2
3
4
5
6
7
8
9
10
11
12
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", "22222");
cache.put("k3", "33333");
cache.put("k4", "4444");
cache.put("k5", "5555");

System.out.println(cache.size()); // 3
System.out.println(cache.getIfPresent("k1")); // null
System.out.println(cache.asMap()); // {k3=33333, k5=5555, k4=4444}

上面例子中,我们通过maximumWeight(15)指定了缓存的最大容量,权重规则为value的长度。k3,k4和k5的value长度加起来为13,所以k1和k2的值存不小了,被驱逐。

设置缓存时间

我们可以设置缓存的有效时间和缓存的活跃时间。

设置缓存的活跃时间为2s:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 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来让键值变为弱引用,这样垃圾收集器在必要的情况下将会工作:

1
Cache<String, String> cache = CacheBuilder.newBuilder().weakKeys().softValues().build();

刷新缓存

可以通过refreshAfterWrite设置缓存自动刷新间隔,或者可以直接调用refresh方法来手动刷新缓存:

1
Cache<String, String> cache = CacheBuilder.newBuilder().refreshAfterWrite(1,TimeUnit.SECONDS).build();

添加多个缓存

可以通过putAll来一次性添加多个缓存:

1
2
3
4
5
6
7
8
9
10
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来删除缓存:

1
2
3
4
5
6
7
8
9
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()删除全部缓存。

我们还可以给删除事件添加监听器:

1
2
3
4
5
6
7
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缓存工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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;
}
}


TOP