九陽神功我覺得是一個(gè)比較基礎(chǔ)的武功间唉,能夠反彈傷害(避免垃圾代碼)栋盹,學(xué)了它施逾,張無忌才能駕馭其他更高級(jí)的武功。
介紹
Guava是Google開源的一個(gè)項(xiàng)目例获,github上面的描述為Google core libraries for Java汉额,其實(shí)就是Google內(nèi)部沉淀的一個(gè)java工具類包。它的一些工具類或思想也被JDK認(rèn)可以及引入了榨汤,比如Optional蠕搜,并且在很多其他開源框架也能看到guava的身影,所以學(xué)習(xí)這個(gè)工具類包對(duì)于我們?nèi)粘i_發(fā)是很有幫助的收壕。工具的作用就是提升效能妓灌。
下面我會(huì)大致通過demo介紹下使用,具體細(xì)節(jié)待各位自己深入了解啼器,總會(huì)有你驚喜的地方旬渠。
引入guava
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.1-jre</version>
</dependency>
字符串操作
Joiner
Joiner用來處理我們常出現(xiàn)的字符串拼接操作。
List<String> 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<String, String> data = ImmutableMap.of("a", "1", "b", "2");
System.out.println(Joiner.on(",").withKeyValueSeparator("-").join(data));
//output:a-1,b-2
Map<String, Integer> data2 = ImmutableMap.of("a", 1, "b", 2);
System.out.println(Joiner.on(",").withKeyValueSeparator("-").join(data2));
//output:a-1,b-2
使用了 guava后代碼是變得多么簡潔端壳,并且這個(gè)工具類是絕對(duì)沒有bug的告丢,我們自己寫這種代碼還保不定出錯(cuò)。
Joiner的使用方式分為三步损谦。
- on方法用來設(shè)置鏈接符
- 在on方法之后 join方法之前 岖免,我們可以做一些擴(kuò)展操作,比如我上面代碼的useForNull是為null值設(shè)置默認(rèn)值照捡。
- join方法用來設(shè)置被操作的集合
除了useForNull之外颅湘,Joiner的擴(kuò)展操作還有
- skipNulls 跳過null值
- withKeyValueSeparator 用來處理對(duì)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<String,String> map = Splitter.on(",").withKeyValueSeparator("-").split("1-2,3-5");
System.out.println(map);
介紹下on后面的擴(kuò)展操作
omitEmptyStrings 用來省略空白
limit 用來限制結(jié)果個(gè)數(shù)栗精,也就是前幾個(gè)分隔符會(huì)生效
trimResults 去除結(jié)果頭尾空格
withKeyValueSeparator 將String轉(zhuǎn)換Map<String,String>
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是相當(dāng)?shù)撵`活悲立,讀者有什么匹配需求看對(duì)應(yīng)API即可鹿寨,我覺得應(yīng)該都能滿足。
集合相關(guān)
新集合
Guava提供了一些自定義的新集合類薪夕,用來解決業(yè)務(wù)開發(fā)中JDK自帶集合滿足不了我們需求的問題脚草。意思就是說,以前你做一個(gè)功能要在老集合上面進(jìn)行復(fù)雜操作原献,但是使用新集合之后馏慨,它直接能滿足你的需求埂淮。
MultiSet
MultiSet的特性是可以用來統(tǒng)計(jì)集合內(nèi)元素出現(xiàn)的次數(shù),在JDK自帶集合類中写隶,我們會(huì)使用以下代碼實(shí)現(xiàn)這個(gè)功能
List<String> words = Lists.newArrayList("a","b","c","b","b","c");
Map<String, Integer> counts = new HashMap<String, Integer>();
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<String> words = Lists.newArrayList("a","b","c","b","b","c");
Multiset<String> multiset1 = HashMultiset.create();
for(String word : words){
multiset1.add(word);
}
System.out.println(multiset1);
Multiset<String> 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的實(shí)現(xiàn)類有很多個(gè)倔撞,這邊我使用了HashMultiset。
具體使用上我們通過create方法初始化Multiset實(shí)例樟澜,通過add增加元素误窖,然后通過count可以得到這個(gè)元素出現(xiàn)的次數(shù)。除了通過add增加元素秩贰,在create初始化的時(shí)候霹俺,我們也能傳入數(shù)組進(jìn)行初始化。
SortedMultiset
SortedMultiset是Multiset的變體毒费,增加了針對(duì)元素次數(shù)的排序功能丙唧,接口實(shí)現(xiàn)類為TreeMultiset
使用方式如下
SortedMultiset<Integer> 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]
不過這個(gè)SortedMultiset是針對(duì)元素進(jìn)行排序的,而不是元素次數(shù)觅玻,所以使用這個(gè)集合類的時(shí)候想际,最好保存數(shù)字類型的元素。并且它的subMultiset是針對(duì)這個(gè)排序規(guī)則來的溪厘,比如我上面是倒序的胡本,使用subMultiset是3到2,而不是2到3畸悬。
guava文檔中SortedMultiset的使用案例是用來統(tǒng)計(jì)接口時(shí)延的分布侧甫,所以key為Long類型。
MultiMap
MultiMap可以理解為對(duì)Map<K, List<V>>或Map<K, Set<V>> 的抽象蹋宦,我們在開發(fā)中也肯定經(jīng)常有統(tǒng)計(jì)一個(gè)key下有哪些value之類場景披粟。
ListMultimap<String,Integer> listMultimap = MultimapBuilder
.treeKeys()
.arrayListValues()
.build();
listMultimap.put("1",1);
listMultimap.put("1",2);
listMultimap.put("2",1);
System.out.println(listMultimap);
List<Integer> value = listMultimap.get("1");
value.add(3);
System.out.println(listMultimap);
listMultimap.removeAll("2");
listMultimap.remove("1",1);
System.out.println(listMultimap);
Map<String, Collection<Integer>> mapView = listMultimap.asMap();
System.out.println(mapView);
SetMultimap<String,Integer> 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 的實(shí)現(xiàn)是定制化的冷冗,可以根據(jù)自己具體需求選擇對(duì)應(yīng)實(shí)現(xiàn)守屉。選擇treeKeys就代表key是有序的。
其次通過get方法拿到的value List是淺拷貝蒿辙。
SetMultimap是另外一種MultiMap的實(shí)現(xiàn)拇泛,不同之處么,Set去重思灌。
BiMap
BiMap提供的功能是反轉(zhuǎn)碰镜,就是說Map<K,V>轉(zhuǎn)換為Map<V,K>。通過這個(gè)數(shù)據(jù)結(jié)構(gòu)能夠滿足你需要通過value去查key的需求习瑰,而不是同時(shí)維護(hù)兩個(gè)map。
BiMap<String,String> 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能夠進(jìn)行反轉(zhuǎn)秽荤。
需要注意的是 value不能重復(fù)甜奄,不然會(huì)報(bào)錯(cuò)柠横。畢竟反轉(zhuǎn)后也是Map,所以value肯定不能重復(fù)课兄。
Table
通過Map這個(gè)結(jié)構(gòu)牍氛,我們可以通過key去找到我們的數(shù)據(jù)。Table的不同之處是烟阐,他提供了兩個(gè)維度去找到我們的數(shù)據(jù)搬俊。
Table<String,String,String> 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"));
三個(gè)泛型分別為Row,Column,Value,所以這個(gè)數(shù)據(jù)類型叫Table。那么問題來了蜒茄,三維唉擂,四維,五維的叫什么檀葛。玩祟。
get方法通過row和column定位value
row/column方法通過Row/Column的維度得到對(duì)應(yīng)的Map
集合工具類
以下集合工具類的好處是
- 提供了一些工廠方法,讓我們創(chuàng)建集合更加方便
我們上面創(chuàng)建新集合屿聋,全部都是通過工廠方法的模式來的空扎,并且guava也提供了JDK原生集合的工廠創(chuàng)建方法,見Lists润讥,Sets转锈,Maps。為什么推崇用工廠方法呢楚殿,因?yàn)樵贘DK8以前泛型不能省略撮慨,代碼冗余。并且工廠方法API除了普通的創(chuàng)建之外也有很多變體勒魔。
List<String> test = new ArrayList<String>();
List<String> test2 = Lists.newArrayList();
List<String> test3 = Lists.newArrayList("1","2");
List<String> test4 = Lists.newArrayList(test);
- 封裝了一些其他方法甫煞,讓我們操縱集合更加方便
這邊我選取一些guava中我覺得好用的集合工具
工具類 | 方法 | 作用 |
---|---|---|
Sets | union | 求兩個(gè)的set并集 |
Sets | intersection | 求兩個(gè)set的交集 |
Sets | difference | 求兩個(gè)set的差集 |
Maps | difference | 返回MapDifference用于比較兩個(gè)Map的并/交/左差/右差集 |
緩存
這邊推薦一個(gè)guava cache的升級(jí)版框架(性能提升),Caffeine冠绢,兼容guava cache api
Guava提供了一個(gè)基于本地緩存的工具類抚吠,很好的封裝了緩存的一些特性,使用方式如下弟胀。
LoadingCache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.maximumWeight(1000)
.weigher(new Weigher<String, String>() {
@Override
public int weigh(String key, String value) {
return key.length();
}
})
.expireAfterAccess(10, TimeUnit.MINUTES)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(new CacheLoader<String, String>() {
@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
同樣的楷力,使用建造者模式進(jìn)行初始化。針對(duì)初始化孵户,我總結(jié)了以下幾點(diǎn)萧朝。
- 設(shè)置緩存容量
- 設(shè)置緩存過期策略
- 設(shè)置緩存生成策略
緩存大小和過期策略都是為了解決就是應(yīng)用內(nèi)存有限以及緩存有效性的問題。
對(duì)于緩存大小有Size和Weight兩種模式夏哭。
Size針對(duì)緩存的個(gè)數(shù)來設(shè)置上限检柬。
上面代碼只是為了說明使用方式,兩種模式只能設(shè)置一種
Weight可以通過Weigher函數(shù)針對(duì)不同的緩存來返回不同Weight,所有緩存累加值不能超過maximumWeight竖配。
當(dāng)緩存容量超過限制值后何址,我們就需要根據(jù)緩存過期策略淘汰一些緩存里逆。
expireAfterAccess會(huì)在緩存read或write后指定時(shí)間后失效。
expireAfterWrite會(huì)在緩存write后指定時(shí)間后失效用爪。
上面代碼只是為了說明使用方式,兩種模式只能設(shè)置一種
緩存生成策略通過CacheLoader來封裝我們緩存的生成邏輯原押。我們可以預(yù)先初始化緩存,當(dāng)get的時(shí)候偎血,如果key不在緩存中诸衔,就會(huì)通過CacheLoader來生成我們的緩存。
最后
上面只介紹了guava中一小部分的常用工具類颇玷,還是很建議讀者全面了解一下笨农,等遇到需求時(shí),也算是一種解決方案亚隙。下面我會(huì)貼上guava的wiki鏈接磁餐,基本常用的都有介紹,有能力的同學(xué)也可以通過看源碼來深入學(xué)習(xí)guava阿弃。