Guava是Google發(fā)布的一個(gè)Java核心工具庫(kù)的開(kāi)源項(xiàng)目,里面囊括了集合、緩存、字符串處理、并發(fā)庫(kù)、通用注解等等。本篇文章將為大家介紹在Java中集合的內(nèi)容,以及應(yīng)用Guava工具庫(kù)中的集合、緩存等內(nèi)容。
集合
普通集合
List<String> list = Lists.newArrayList();
Set<String> set = Sets.newHashSet();
Map<String, String> map = Maps.newHashMap();
Set 取交集、并集、差集
HashSet<Integer> setA = Sets.newHashSet(1, 2, 3, 4, 5);
HashSet<Integer> setB = Sets.newHashSet(4, 5, 6, 7, 8);
Sets.SetView<Integer> union = Sets.union(setA, setB);
System.out.println("union:" + union);
Sets.SetView<Integer> difference = Sets.difference(setA, setB);
System.out.println("difference:" + difference);
Sets.SetView<Integer> intersection = Sets.intersection(setA, setB);
System.out.println("intersection:" + intersection);
map 取交集、并集、差集
HashMap<String, Integer> mapA = Maps.newHashMap();
mapA.put("a", 1);
mapA.put("b", 2);
mapA.put("c", 3);
HashMap<String, Integer> mapB = Maps.newHashMap();
mapB.put("b", 20);
mapB.put("c", 3);
mapB.put("d", 4);
MapDifference<String, Integer> differenceMap = Maps.difference(mapA, mapB);
Map<String, MapDifference.ValueDifference<Integer>> entriesDiffering = differenceMap.entriesDiffering();
//左邊差集
Map<String, Integer> entriesOnlyLeft = differenceMap.entriesOnlyOnLeft();
//右邊差集
Map<String, Integer> entriesOnlyRight = differenceMap.entriesOnlyOnRight();
//交集
Map<String, Integer> entriesInCommon = differenceMap.entriesInCommon();
System.out.println(entriesDiffering); // {b=(2, 20)}
System.out.println(entriesOnlyLeft); // {a=1}
System.out.println(entriesOnlyRight); // {d=4}
System.out.println(entriesInCommon); // {c=3}
不可變集合(immutable)
不可變集合的特性有:
- 在多線(xiàn)程操作下,是線(xiàn)程安全的;
- 所有不可變集合會(huì)比可變集合更有效的利用資源;
- 中途不可改變。
如果你的需求是想創(chuàng)建一個(gè)一經(jīng)初始化后就不能再被改變的集合那么它適合你,因?yàn)檫@些工具類(lèi)根本就沒(méi)給你提供修改的 API,這意味著你連犯錯(cuò)誤的機(jī)會(huì)都沒(méi)有。
ImmutableList<Integer> iList = ImmutableList.of(12,54,87);
ImmutableSet<Integer> iSet = ImmutableSet.of(354,54,764,354);
ImmutableMap<String, Integer> iMap = ImmutableMap.of("k1", 453, "k2", 534);
以上 Immutable 開(kāi)頭的相關(guān)集合類(lèi)的 add、remove 方法都被聲明為 deprecated。當(dāng)你手誤點(diǎn)到了這些方法發(fā)現(xiàn)是 deprecated 的時(shí)候你不會(huì)還想著使用吧。
注意:每個(gè)Guava immutable集合類(lèi)的實(shí)現(xiàn)都拒絕 null 值。
有趣的集合
MultiSet: 無(wú)序+可重復(fù)
我們映像中的 Set 應(yīng)該是無(wú)序的,元素不可重復(fù)的。MultiSet 顛覆了三觀(guān),因?yàn)樗梢灾貜?fù)。
定義一個(gè) MultiSet 并添加元素:
Multiset<Integer> set = HashMultiset.create();
set.add(3);
set.add(3);
set.add(4);
set.add(5);
set.add(4);
你還可以添加指定個(gè)數(shù)的同一個(gè)元素:
set.add(7, 3);
這表示你想添加 3 個(gè) 7。
打印出來(lái)的 MultiSet 也很有意思:
[3 x 2, 4 x 2, 5, 7 x 3]
2個(gè)3,2個(gè)4,一個(gè)5,3個(gè)7。
獲取某個(gè)元素的個(gè)數(shù):
int count = set.count(3);
這個(gè)工具類(lèi)確實(shí)很有意思,幫我們實(shí)現(xiàn)了 word count。
Multimap :key 可以重復(fù)的 map
這個(gè) map 也很有意思。正常的 map 為了區(qū)分不同的 key,它倒好,直接給你來(lái)一樣的 key 。
Multimap<String, String> map = LinkedHashMultimap.create();
map.put("key", "haha");
map.put("key", "haha1");
Collection<String> key = map.get("key");
System.out.println(key);
使用很簡(jiǎn)單,用一個(gè) key 可以獲取到該 key 對(duì)應(yīng)的兩個(gè)值,結(jié)果用 list 返回。恕我無(wú)知,我還沒(méi)想到這個(gè) map 能夠使用的場(chǎng)景。
Multimap 提供了多種實(shí)現(xiàn):
Multimap 實(shí)現(xiàn) | key 字段類(lèi)型 | value 字段類(lèi)型 |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap | LinkedHashMap | LinkedList |
LinkedHashMultimap | LinkedHashMap | LinkedHashSet |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
雙向 Map
(Bidirectional Map) 鍵與值都不能重復(fù)
這個(gè)稍稍正常一點(diǎn)。如果 key 重復(fù)了則會(huì)覆蓋 key ,如果 value 重復(fù)了則會(huì)報(bào)錯(cuò)。
public static void main(String[] args) {
BiMap<String, String> biMap = HashBiMap.create();
biMap.put("key", "haha");
biMap.put("key", "haha1");
biMap.put("key1", "haha");
String value = biMap.get("key");
System.out.println(value);
}
上面的示例中鍵 ”key“ 有兩個(gè),運(yùn)行可以發(fā)現(xiàn) get 的時(shí)候會(huì)用 ”haha1" 覆蓋 ”haha“,另外 value 為 ”haha“ 也有兩個(gè),你會(huì)發(fā)現(xiàn)運(yùn)行上面的代碼不會(huì)報(bào)錯(cuò),這是因?yàn)?”key“ 對(duì)應(yīng)的 value 已經(jīng)被 "haha1" 覆蓋了。否則是會(huì)報(bào)錯(cuò)。
雙鍵 map - 超級(jí)實(shí)用
雙鍵的 map ,我突然感覺(jué)我發(fā)現(xiàn)了新大陸。比如我有一個(gè)業(yè)務(wù)場(chǎng)景是:根據(jù)職位和部門(mén)將公司人員區(qū)分開(kāi)來(lái)。key 可以用職位 + 部門(mén)組成一個(gè)字符串,那我們有了雙鍵 map 之后就沒(méi)這種煩惱。
public static void main(String[] args) {
Table<String, String, List<Object>> tables = HashBasedTable.create();
tables.put("財(cái)務(wù)部", "總監(jiān)", Lists.newArrayList());
tables.put("財(cái)務(wù)部", "職員",Lists.newArrayList());
tables.put("法務(wù)部", "助理",Lists.newArrayList());
System.out.println(tables);
}
工具類(lèi)
JDK里大家耳熟能詳?shù)氖?code>Collections 這個(gè)集合工具類(lèi), 提供了一些基礎(chǔ)的集合處理轉(zhuǎn)換功能, 但是實(shí)際使用里很多需求并不是簡(jiǎn)單的排序, 或者比較數(shù)值大小, 然后 Guava 在此基礎(chǔ)上做了許多的改進(jìn)優(yōu)化, 可以說(shuō)是 Guava 最為成熟/流行的模塊之一。
- 數(shù)組相關(guān):Lists
- 集合相關(guān):Sets
- map 相關(guān):Maps
連接符(Joiner)和分隔符(Splitter)
Joiner 做為連接符的使用非常簡(jiǎn)單,下例是將 list 轉(zhuǎn)為使用連接符連接的字符串:
List<Integer> list = Lists.newArrayList();
list.add(34);
list.add(64);
list.add(267);
list.add(865);
String result = Joiner.skipNulls().on("-").join(list);
System.out.println(result);
輸出:34-64-267-865
將 map 轉(zhuǎn)為自定義連接符連接的字符串:
Map<String, Integer> map = Maps.newHashMap();
map.put("key1", 45);
map.put("key2",234);
String result = Joiner.on(",").withKeyValueSeparator("=").join(map);
System.out.println(result);
輸出:
key1=45,key2=234
分隔符 Splitter 的使用也很簡(jiǎn)單:
String str = "1-2-3-4-5-6";
List<String> list = Splitter.on("-").splitToList(str);
System.out.println(list);
輸出:
[1, 2, 3, 4, 5, 6]
如果字符串中帶有空格,還可以先去掉空格:
String str = "1-2-3-4- 5- 6 ";
List<String> list = Splitter.on("-").omitEmptyStrings().trimResults().splitToList(str);
System.out.println(list);
將 String 轉(zhuǎn)為 map:
String str = "key1=54,key2=28";
Map<String,String> map = Splitter.on(",").withKeyValueSeparator("=").split(str);
System.out.println(map);
輸出:
{key1=54, key2=28}
Comparator 的實(shí)現(xiàn)
Java 提供了 Comparator 可以用來(lái)對(duì)對(duì)象進(jìn)行排序。Guava 提供了排序器 Ordering 類(lèi)封裝了很多實(shí)用的操作。
Ordering 提供了一些有用的方法:
- natural() 對(duì)可排序類(lèi)型做自然排序,如數(shù)字按大小,日期按先后排序
- usingToString() 按對(duì)象的字符串形式做字典排序[lexicographical ordering]
- from(Comparator) 把給定的Comparator轉(zhuǎn)化為排序器
- reverse() 獲取語(yǔ)義相反的排序器
- nullsFirst() 使用當(dāng)前排序器,但額外把null值排到最前面。
- nullsLast() 使用當(dāng)前排序器,但額外把null值排到最后面。
- compound(Comparator) 合成另一個(gè)比較器,以處理當(dāng)前排序器中的相等情況。 lexicographical() 基于處理類(lèi)型T的排序器,返回該類(lèi)型的可迭代對(duì)象Iterable的排序器。
- onResultOf(Function) 對(duì)集合中元素調(diào)用Function,再按返回值用當(dāng)前排序器排序。
示例:
UserInfo build = UserInfo.builder().uid(234L).gender(1).build();
UserInfo build1 = UserInfo.builder().uid(4354L).gender(0).build();
Ordering<UserInfo> byOrdering = Ordering.natural().nullsFirst().onResultOf((Function<UserInfo, Comparable<Integer>>) input -> input.getGender());
System.out.println(byOrdering.compare(build1, build));
build 的 gender 大于 build1 的,所以返回 -1,反之返回 1。
統(tǒng)計(jì)中間代碼運(yùn)行時(shí)間
Stopwatch 類(lèi)提供了時(shí)間統(tǒng)計(jì)的功能,相當(dāng)于幫你封裝了調(diào)用 System.currentTimeMillis() 的邏輯。
Stopwatch stopwatch = Stopwatch.createStarted();
try {
//TODO 模擬業(yè)務(wù)邏輯
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
long nanos = stopwatch.elapsed(TimeUnit.SECONDS);
System.out.println(nanos);
Guava Cache - 本地緩存組件
Guava Cache 在日常的使用中非常地頻繁,甚至都沒(méi)有意識(shí)到這是第三方提供的工具類(lèi)而是把它當(dāng)成了 JDK 自帶的實(shí)現(xiàn)。
// LoadingCache是Cache的緩存實(shí)現(xiàn)
LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
//設(shè)置緩存大小
.maximumSize(1000)
//設(shè)置到期時(shí)間
.expireAfterWrite(10, TimeUnit.MINUTES)
//設(shè)置緩存里的值兩分鐘刷新一次
.refreshAfterWrite(2, TimeUnit.MINUTES)
//開(kāi)啟緩存的統(tǒng)計(jì)功能
.recordStats()
//構(gòu)建緩存
.build(new CacheLoader<String, Object>() {
//此處實(shí)現(xiàn)如果根據(jù)key找不到value需要去如何獲取
@Override
public Object load(String s) throws Exception {
return new Object();
}
//如果批量加載有比反復(fù)調(diào)用load更優(yōu)的方法則重寫(xiě)這個(gè)方法
@Override
public Map<String, Object> loadAll(Iterable<? extends String> keys) throws Exception {
return super.loadAll(keys);
}
});
設(shè)置本地緩存使用 CacheBuilder.newBuilder(),支持設(shè)置緩存大小,緩存過(guò)期時(shí)間,緩存刷新頻率等等。如果你想統(tǒng)計(jì)緩存的命中率, Guava Cache 也提供了這種能力幫你匯總當(dāng)前緩存是否有效。
同時(shí)緩存如果因?yàn)槟撤N原因未自動(dòng)刷新或者清除,Guava Cache 也支持用戶(hù)手動(dòng)調(diào)用 API 刷新或者清除緩存。
cache.invalidateAll();//清除所有緩存項(xiàng)
//清理的時(shí)機(jī):在寫(xiě)操作時(shí)順帶做少量的維護(hù)工作,或者偶爾在讀操作時(shí)做——如果寫(xiě)操作實(shí)在太少的話(huà)
//如果想自己維護(hù)則可以調(diào)用Cache.cleanUp();
cache.cleanUp();
//另外有時(shí)候需要緩存中的數(shù)據(jù)做出變化重載一次,這個(gè)過(guò)程可以異步執(zhí)行
cache.refresh("key");
單機(jī)限流工具類(lèi) - RateLimiter
常用的限流算法有 漏桶算法、令牌桶算法。這兩種算法各有側(cè)重點(diǎn):
- 漏桶算法:漏桶的意思就像一個(gè)漏斗一樣,水一滴一滴的滴下去,流出是勻速的。當(dāng)訪(fǎng)問(wèn)量過(guò)大的時(shí)候這個(gè)漏斗就會(huì)積水。漏桶算法的實(shí)現(xiàn)依賴(lài)隊(duì)列,一個(gè)處理器從隊(duì)頭依照固定頻率取出數(shù)據(jù)進(jìn)行處理。如果請(qǐng)求量過(guò)大導(dǎo)致隊(duì)列堆滿(mǎn)那么新來(lái)的請(qǐng)求就會(huì)被拋棄。漏桶一般按照固定的速率流出。
- 令牌桶則是存放固定容量的令牌,按照固定速率從桶中取出令牌。初始給桶中添加固定容量令牌,當(dāng)桶中令牌不夠取出的時(shí)候則拒絕新的請(qǐng)求。令牌桶不限制取出令牌的速度,只要有令牌就能處理。所以令牌桶允許一定程度的突發(fā),而漏桶主要目的是平滑流出。
RateLimiter 使用了令牌桶算法,提供兩種限流的實(shí)現(xiàn)方案:
- 平滑突發(fā)限流(SmoothBursty)
- 平滑預(yù)熱限流(SmoothWarmingUp)
實(shí)現(xiàn)平滑突發(fā)限流通過(guò) RateLimiter 提供的靜態(tài)方法來(lái)創(chuàng)建:
RateLimiter r = RateLimiter.create(5);
while (true) {
System.out.println("get 1 tokens: " + r.acquire() + "s");
}
輸出:
get 1 tokens: 0.0s
get 1 tokens: 0.197059s
get 1 tokens: 0.195338s
get 1 tokens: 0.196918s
get 1 tokens: 0.19955s
get 1 tokens: 0.199062s
get 1 tokens: 0.195589s
get 1 tokens: 0.195061s
......
設(shè)置每秒放置的令牌數(shù)為 5 個(gè),基本 0.2s 一次符合每秒 5 個(gè)的設(shè)置。保證每秒不超過(guò) 5 個(gè)達(dá)到了平滑輸出的效果。
在沒(méi)有請(qǐng)求使用令牌桶的時(shí)候,令牌會(huì)先創(chuàng)建好放在桶中,所以此時(shí)如果突然有突發(fā)流量進(jìn)來(lái),由于桶中有足夠的令牌可以快速響應(yīng)。RateLimiter 在沒(méi)有足夠令牌發(fā)放時(shí)采用滯后處理的方式,前一個(gè)請(qǐng)求獲取令牌所需等待的時(shí)間由下一次請(qǐng)求來(lái)承受。
平滑預(yù)熱限流并不會(huì)像平滑突發(fā)限流一樣先將所有的令牌創(chuàng)建好,它啟動(dòng)后會(huì)有一段預(yù)熱期,逐步將分發(fā)頻率提升到配置的速率。
比如下面例子創(chuàng)建一個(gè)平均分發(fā)令牌速率為 2,預(yù)熱期為 3 分鐘。由于設(shè)置了預(yù)熱時(shí)間是 3 秒,令牌桶一開(kāi)始并不會(huì) 0.5 秒發(fā)一個(gè)令牌,而是形成一個(gè)平滑線(xiàn)性下降的坡度,頻率越來(lái)越高,在 3 秒鐘之內(nèi)達(dá)到原本設(shè)置的頻率,以后就以固定的頻率輸出。這種功能適合系統(tǒng)剛啟動(dòng)需要一點(diǎn)時(shí)間來(lái)“熱身”的場(chǎng)景。
RateLimiter r = RateLimiter.create(2, 3, TimeUnit.SECONDS);
while (true) {
System.out.println("get 1 tokens: " + r.acquire(1) + "s");
System.out.println("get 1 tokens: " + r.acquire(1) + "s");
System.out.println("end");
}
輸出:
get 1 tokens: 0.0s
get 1 tokens: 1.33068s
end
get 1 tokens: 0.995792s
get 1 tokens: 0.662838s
end
get 1 tokens: 0.494775s
get 1 tokens: 0.497293s
end
get 1 tokens: 0.49966s
get 1 tokens: 0.49625s
end
從上面的輸出看前面兩次獲取令牌都很耗時(shí),往后就越來(lái)越趨于平穩(wěn)。
今天給大家介紹的常用的 Guava 工具類(lèi)就這些,不過(guò) JDK8 開(kāi)始 Java官方 API 也在完善,比如像字符串相關(guān)的功能 JDK也很強(qiáng)大。都是工具,哪個(gè)好用就用哪個(gè)。
以上就是關(guān)于Google發(fā)布的Java核心工具包Guava的使用介紹就到此結(jié)束了,想要了解更多Java Guava工具包的其他內(nèi)容,請(qǐng)搜索W3Cschool以前的文章或繼續(xù)瀏覽下面的相關(guān)文章!