九阳神功我觉得是一个比较基础的武功,能够反弹伤害(避免垃圾代码),学了它,张无忌才能驾驭其他更高级的武功。
介绍
Guava是Google开源的一个项目,github上面的描述为Google core libraries for Java,其实就是Google内部沉淀的一个java工具类包。它的一些工具类或思想也被JDK认可以及引入了,比如Optional,并且在很多其他开源框架也能看到guava的身影,所以学习这个工具类包对于我们日常开发是很有帮助的。工具的作用就是提升效能。
下面我会大致通过demo介绍下使用,具体细节待各位自己深入了解,总会有你惊喜的地方。
引入guava
com.google.guava
guava
27.1-jre
字符串操作
Joiner
Joiner用来处理我们常出现的字符串拼接操作。
List words = Lists.newArrayList("123","456","789",null);
//不使用guava
StringBuilder sb = new StringBuilder();
for(String word : words){
if(word==null){
sb.append("default");
}else {
sb.append(word);
}
sb.append(",");
}
if(sb.length()>1){
sb.deleteCharAt(sb.length()-1);
}
System.out.println(sb.toString());
//使用guava
System.out.println(Joiner.on(",").useForNull("default").join(words));
System.out.println(Joiner.on(",").skipNulls().join(words));
Map data = ImmutableMap.of("a", "1", "b", "2");
System.out.println(Joiner.on(",").withKeyValueSeparator("-").join(data));
//output:a-1,b-2
Map data2 = ImmutableMap.of("a", 1, "b", 2);
System.out.println(Joiner.on(",").withKeyValueSeparator("-").join(data2));
//output:a-1,b-2
使用了 guava后代码是变得多么简洁,并且这个工具类是绝对没有bug的,我们自己写这种代码还保不定出错。
Joiner的使用方式分为三步。
- on方法用来设置链接符
- 在on方法之后 join方法之前 ,我们可以做一些扩展操作,比如我上面代码的useForNull是为null值设置默认值。
- join方法用来设置被操作的集合
除了useForNull之外,Joiner的扩展操作还有
- skipNulls 跳过null值
- withKeyValueSeparator 用来处理对Map的输出
Splitter
Splitter思想和Joiner类似,我们直接看例子
Splitter.on(",").omitEmptyStrings().splitToList("123,456,789,,23").forEach(a->{
System.out.println(a);
});
Splitter.on(",").limit(2).splitToList("123,456,789,,23").forEach(a->{
System.out.println(a);
});
Splitter.on(",").trimResults().splitToList("12 3, 456 ,789,,23").forEach(a->{
System.out.println(a);
});
Map map = Splitter.on(",").withKeyValueSeparator("-").split("1-2,3-5");
System.out.println(map);
介绍下on后面的扩展操作
omitEmptyStrings 用来省略空白
limit 用来限制结果个数,也就是前几个分隔符会生效
trimResults 去除结果头尾空格
withKeyValueSeparator 将String转换Map
CharMatcher
CharMatcher用来从字符串匹配出自己想要的那部分,操作也被抽象为两步
- 选择匹配模式
- 选择如何处理这些匹配到的字符
不理解?看下demo就清楚了
System.out.println(CharMatcher.inRange('0','9').retainFrom("asfds12312fds444"));
//12312444
System.out.println(CharMatcher.inRange('0','9').removeFrom("asfds12312fds444"));
//asfdsfds
System.out.println(CharMatcher.inRange('0','9').or(CharMatcher.whitespace()).retainFrom("as fds123 12 fds444"));
// 123 12 444
CharMatcher是相当的灵活,读者有什么匹配需求看对应API即可,我觉得应该都能满足。
集合相关
新集合
Guava提供了一些自定义的新集合类,用来解决业务开发中JDK自带集合满足不了我们需求的问题。意思就是说,以前你做一个功能要在老集合上面进行复杂操作,但是使用新集合之后,它直接能满足你的需求。
MultiSet
MultiSet的特性是可以用来统计集合内元素出现的次数,在JDK自带集合类中,我们会使用以下代码实现这个功能
List words = Lists.newArrayList("a","b","c","b","b","c");
Map counts = new HashMap();
for (String word : words) {
Integer count = counts.get(word);
if (count == null) {
counts.put(word, 1);
} else {
counts.put(word, count + 1);
}
}
//output
// {a=1, b=3, c=2}
但是是用了MultiSet后
List words = Lists.newArrayList("a","b","c","b","b","c");
Multiset multiset1 = HashMultiset.create();
for(String word : words){
multiset1.add(word);
}
System.out.println(multiset1);
Multiset multiset2 = HashMultiset.create(words);
multiset2.add("d",4);
System.out.println(multiset2);
//output
//[a, b x 3, c x 2]
//[a, b x 3, c x 2, d x 4]
//1
Multiset的实现类有很多个,这边我使用了HashMultiset。
具体使用上我们通过create方法初始化Multiset实例,通过add增加元素,然后通过count可以得到这个元素出现的次数。除了通过add增加元素,在create初始化的时候,我们也能传入数组进行初始化。
SortedMultiset
SortedMultiset是Multiset的变体,增加了针对元素次数的排序功能,接口实现类为TreeMultiset
使用方式如下
SortedMultiset sortedMultiset = TreeMultiset.create();
sortedMultiset.add(2,3);
sortedMultiset.add(3,5);
sortedMultiset.add(4,4);
System.out.println(sortedMultiset);
sortedMultiset = sortedMultiset.descendingMultiset();
System.out.println(sortedMultiset);
System.out.println(sortedMultiset.firstEntry().getElement());
sortedMultiset = sortedMultiset.subMultiset(3,BoundType.OPEN,2,BoundType.CLOSED);
System.out.println(sortedMultiset);
//output
//[2 x 3, 3 x 5, 4 x 4]
//[4 x 4, 3 x 5, 2 x 3]
//4
//[2 x 3]
不过这个SortedMultiset是针对元素进行排序的,而不是元素次数,所以使用这个集合类的时候,最好保存数字类型的元素。并且它的subMultiset是针对这个排序规则来的,比如我上面是倒序的,使用subMultiset是3到2,而不是2到3。
guava文档中SortedMultiset的使用案例是用来统计接口时延的分布,所以key为Long类型。
MultiMap
MultiMap可以理解为对Map
ListMultimap listMultimap = MultimapBuilder
.treeKeys()
.arrayListValues()
.build();
listMultimap.put("1",1);
listMultimap.put("1",2);
listMultimap.put("2",1);
System.out.println(listMultimap);
List value = listMultimap.get("1");
value.add(3);
System.out.println(listMultimap);
listMultimap.removeAll("2");
listMultimap.remove("1",1);
System.out.println(listMultimap);
Map> mapView = listMultimap.asMap();
System.out.println(mapView);
SetMultimap setMultimap = MultimapBuilder
.treeKeys()
.hashSetValues()
.build();
//output
//{1=[1, 2], 2=[1]}
//{1=[1, 2, 3], 2=[1]}
//{1=[2, 3]}
//{1=[2, 3]}
首先我们可以看到MultiMap的初始化采用建造者模式,key和value 的实现是定制化的,可以根据自己具体需求选择对应实现。选择treeKeys就代表key是有序的。
其次通过get方法拿到的value List是浅拷贝。
SetMultimap是另外一种MultiMap的实现,不同之处么,Set去重。
BiMap
BiMap提供的功能是反转,就是说Map
BiMap biMap = HashBiMap.create();
biMap.put("scj","programmer");
//biMap.put("scj2","programmer");
System.out.println(biMap.get("scj"));
System.out.println(biMap.inverse().get("programmer"));
//output
//programmer
//scj
通过inverse能够进行反转。
需要注意的是 value不能重复,不然会报错。毕竟反转后也是Map,所以value肯定不能重复。
Table
通过Map这个结构,我们可以通过key去找到我们的数据。Table的不同之处是,他提供了两个维度去找到我们的数据。
Table table = HashBasedTable.create();
table.put("male","programmer","scj");
table.put("female","beauty","ss");
table.put("female","programmer","s2");
System.out.println(table.get("male","programmer"));
System.out.println(table.row("male").get("programmer"));
System.out.println(table.column("programmer").get("female"));
三个泛型分别为Row,Column,Value,所以这个数据类型叫Table。那么问题来了,三维,四维,五维的叫什么。。
get方法通过row和column定位value
row/column方法通过Row/Column的维度得到对应的Map
集合工具类
以下集合工具类的好处是
- 提供了一些工厂方法,让我们创建集合更加方便
我们上面创建新集合,全部都是通过工厂方法的模式来的,并且guava也提供了JDK原生集合的工厂创建方法,见Lists,Sets,Maps。为什么推崇用工厂方法呢,因为在JDK8以前泛型不能省略,代码冗余。并且工厂方法API除了普通的创建之外也有很多变体。
List test = new ArrayList();
List test2 = Lists.newArrayList();
List test3 = Lists.newArrayList("1","2");
List test4 = Lists.newArrayList(test);
- 封装了一些其他方法,让我们操纵集合更加方便
这边我选取一些guava中我觉得好用的集合工具
工具类 | 方法 | 作用 |
---|---|---|
Sets | union | 求两个的set并集 |
Sets | intersection | 求两个set的交集 |
Sets | difference | 求两个set的差集 |
Maps | difference | 返回MapDifference用于比较两个Map的并/交/左差/右差集 |
缓存
这边推荐一个guava cache的升级版框架(性能提升),Caffeine,兼容guava cache api
Guava提供了一个基于本地缓存的工具类,很好的封装了缓存的一些特性,使用方式如下。
LoadingCache cache = CacheBuilder.newBuilder()
.maximumSize(100)
.maximumWeight(1000)
.weigher(new Weigher() {
@Override
public int weigh(String key, String value) {
return key.length();
}
})
.expireAfterAccess(10, TimeUnit.MINUTES)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(new CacheLoader() {
@Override
public String load(String key) throws Exception {
return key+"cache";
}
});
cache.put("test","23333");
System.out.println(cache.get("test"));
System.out.println(cache.get("scj"));
//output
//2333
//scjcache
同样的,使用建造者模式进行初始化。针对初始化,我总结了以下几点。
- 设置缓存容量
- 设置缓存过期策略
- 设置缓存生成策略
缓存大小和过期策略都是为了解决就是应用内存有限以及缓存有效性的问题。
对于缓存大小有Size和Weight两种模式。
Size针对缓存的个数来设置上限。
上面代码只是为了说明使用方式,两种模式只能设置一种
Weight可以通过Weigher函数针对不同的缓存来返回不同Weight,所有缓存累加值不能超过maximumWeight。
当缓存容量超过限制值后,我们就需要根据缓存过期策略淘汰一些缓存。
expireAfterAccess会在缓存read或write后指定时间后失效。
expireAfterWrite会在缓存write后指定时间后失效。
上面代码只是为了说明使用方式,两种模式只能设置一种
缓存生成策略通过CacheLoader来封装我们缓存的生成逻辑。我们可以预先初始化缓存,当get的时候,如果key不在缓存中,就会通过CacheLoader来生成我们的缓存。
最后
上面只介绍了guava中一小部分的常用工具类,还是很建议读者全面了解一下,等遇到需求时,也算是一种解决方案。下面我会贴上guava的wiki链接,基本常用的都有介绍,有能力的同学也可以通过看源码来深入学习guava。
参考
guava wiki