Guava 在 Java 服务中的应用与解析

前言

Guava 是一个由 Google 开发、基于 Java 的类库集合的扩展项目,包括 collections, caching, primitives support, concurrency libraries, common annotations, string processing, I/O, 等等。

这些高质量的 API 可以让我们的 Java 代码更加优雅,更加简洁,让我们工作更加轻松愉悦。

1. Guava 的引入与基本使用

Guava 的引入十分简单,以 Maven 包管理为例,只需在自己项目的 pom.xml 文件中引入下面内容:

1
2
3
4
5
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.2-jre</version>
</dependency>

完成了上面的操作就可以在项目中直接使用 Guava 的各项功能了,首先介绍 collections。JDK 中提供了 Set,能保证元素都不重复,但是如过需要存放重复的元素呢?Guava 中直接提供了 MultilSet:

1
2
3
4
HashMultiset<String> multiSet = HashMultiset.create();
List wordList = Lists.newArrayList("a", "a", "b", "c");
multiSet.addAll(wordList);
Integer count = multiSet.count(“a”);

MultilSet 可以用于允许某个操作对某个元素有次数限制,但是这个限制不为 1,每次加入 MultilSet 时可以判断这个元素是否超过了限制,没有超过就可以放入其中。

类似的还有 MultilMap,举个记名投票的例子。所有选票都放在一个 List 里面,List 的每个元素包括投票人和选举人的名字。如果使用JDK我们可以这样写 :

1
2
3
4
5
6
7
8
9
HashMap<String, HashSet<String>> hMap = new HashMap<String, HashSet<String>>();
for(Ticket ticket: tickets){
HashSet<String> set = hMap.get(ticket.getCandidate());
if(set == null){
set = new HashSet<String>();
hMap.put(ticket.getCandidate(), set);
}
set.add(ticket.getVoter());
}

但是现在 Guava 提供了 MultilMap,我们可以使用更加优雅的方法:

1
2
3
4
HashMultimap<String, String> map = HashMultimap.create();
for(Ticket ticket: tickets){
map.put(ticket.getCandidate(), ticket.getVoter());
}

下面再解释 BiMap,平常在开发中我们会遇到下面的需求:

根据 Map 中 value 的值来反推 key 的值,需要这样来操作:

1
2
3
4
5
6
for(Map.Entry<User, Address> entry : map.entreSet()){
if(entry.getValue().equals(anAddess)){
return entry.getKey();
}
}
return null;

但是如果使用 Guava 的 BiMap,只需要一句话就可以解决问题:

1
return biMap.inverse().get(anAddess);

ConcurrentMap 是我们最常用的数据结构之一,Guava 中提供了 MapMaker 来创建各种功能的 ConcurrentMap,具体功能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//设置并发度为8
ConcurrentMap<String, Object> map1 = new MapMaker()
.concurrencyLevel(8)
.makeMap();
//构造用各种不同reference作为key和value的Map:
ConcurrentMap<String, Object> map2 = new MapMaker()
.softKeys()
.weakValues()
.makeMap();
//构造有自动移除时间过期项的Map:
ConcurrentMap<String, Object> map3 = new MapMaker()
.expireAfterWrite(30, TimeUnit.SECONDS)
.makeMap();

2. Guava 的本地缓存功能

Guava Cache 提供了功能强大并且使用方便的本地缓存功能,在项目中我们经常能遇到需要使用本地缓存的场景,大家首先想到的便是 HashMap,但是每次都需要自己实现相应的增删改查以及过期策略,十分繁琐还极易出错,而 Guava Cache 很好地解决了这个问题。

LoadingCache 是 Guava 本地缓存中最基本的类,提供了各种策略的缓存功能。具体功能如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 超时缓存:数据写入缓存超过一定时间自动刷新
* @param duration
* @param timeUtil
*/
public BaseCache(long duration, TimeUnit timeUtil) {
cache = CacheBuilder.newBuilder()
.expireAfterWrite(duration, timeUtil)
.build(new CacheLoader<K, V>() {
@Override
public V load(K k) throws Exception
{
return loadData(k);
}
});
}

注意上面的 LoadData 方法就是 LocaingCache 的精华所在,当缓存 miss 时就可以通过自定义的策略来加载数据,可以是从数据库也可以是其他途径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 限容缓存:缓存数据个数不能超过maxSize
* @param maxSize
*/
public BaseCache(long maxSize) {
cache = CacheBuilder.newBuilder()
.maximumSize(maxSize)
.build(new CacheLoader<K, V>() {
@Override
public V load(K k) throws Exception
{
return loadData(k);
}
});
}

上述缓存策略没有设置超时,而是设置了最大的缓存个数,超过后就会按照策略进行驱逐。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 权重缓存:缓存数据权重和不能超过maxWeight
* @param maxWeight
* @param weigher:权重函数类,需要实现计算元素权重的函数
*/
public BaseCache(long maxWeight, Weigher<K, V> weigher) {
cache = CacheBuilder.newBuilder()
.maximumWeight(maxWeight)
.weigher(weigher)
.build(new CacheLoader<K, V>() {
@Override
public V load(K k) throws Exception
{
return loadData(k);
}
});
}

利用 CacheBuilder 还可以构造另一种不带 loading 的普通缓存,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
Cache<Object, Object> cache = CacheBuilder.newBuilder().build();
// 放入覆盖一个缓存
cache.put("k1", "v1");
// 获取一个缓存,如果该缓存不存在则返回一个null值
Object value = cache.getIfPresent("k1");
// 获取缓存,当缓存不存在时,则通Callable进行加载并返回,该操作是原子的
Object getValue = cache.get("k1", new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
});

3. 总结

本文介绍了如何在 Java 项目中引入 Guava,着重介绍了最常用的 Collections 和 Cache 功能,他们不仅实现了一些常用但 JDK 却不支持的功能,并且写法上比较优雅,代码可读性也较高。

----本文结束 感谢您的阅读----