Guava是Google發(fā)布的一個Java核心工具庫的開源項目,里面囊括了集合、緩存、字符串處理、并發(fā)庫、通用注解等等。本篇文章將為大家介紹在Java中集合的內(nèi)容,以及應(yīng)用Guava工具庫中的集合、緩存等內(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)
不可變集合的特性有:
- 在多線程操作下,是線程安全的;
- 所有不可變集合會比可變集合更有效的利用資源;
- 中途不可改變。
如果你的需求是想創(chuàng)建一個一經(jīng)初始化后就不能再被改變的集合那么它適合你,因為這些工具類根本就沒給你提供修改的 API,這意味著你連犯錯誤的機會都沒有。
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 開頭的相關(guān)集合類的 add、remove 方法都被聲明為 deprecated。當你手誤點到了這些方法發(fā)現(xiàn)是 deprecated 的時候你不會還想著使用吧。
注意:每個Guava immutable集合類的實現(xiàn)都拒絕 null 值。
有趣的集合
MultiSet: 無序+可重復(fù)
我們映像中的 Set 應(yīng)該是無序的,元素不可重復(fù)的。MultiSet 顛覆了三觀,因為它可以重復(fù)。
定義一個 MultiSet 并添加元素:
Multiset<Integer> set = HashMultiset.create();
set.add(3);
set.add(3);
set.add(4);
set.add(5);
set.add(4);
你還可以添加指定個數(shù)的同一個元素:
set.add(7, 3);
這表示你想添加 3 個 7。
打印出來的 MultiSet 也很有意思:
[3 x 2, 4 x 2, 5, 7 x 3]
2個3,2個4,一個5,3個7。
獲取某個元素的個數(shù):
int count = set.count(3);
這個工具類確實很有意思,幫我們實現(xiàn)了 word count。
Multimap :key 可以重復(fù)的 map
這個 map 也很有意思。正常的 map 為了區(qū)分不同的 key,它倒好,直接給你來一樣的 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);
使用很簡單,用一個 key 可以獲取到該 key 對應(yīng)的兩個值,結(jié)果用 list 返回。恕我無知,我還沒想到這個 map 能夠使用的場景。
Multimap 提供了多種實現(xiàn):
Multimap 實現(xiàn) | key 字段類型 | value 字段類型 |
---|---|---|
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ù)
這個稍稍正常一點。如果 key 重復(fù)了則會覆蓋 key ,如果 value 重復(fù)了則會報錯。
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“ 有兩個,運行可以發(fā)現(xiàn) get 的時候會用 ”haha1" 覆蓋 ”haha“,另外 value 為 ”haha“ 也有兩個,你會發(fā)現(xiàn)運行上面的代碼不會報錯,這是因為 ”key“ 對應(yīng)的 value 已經(jīng)被 "haha1" 覆蓋了。否則是會報錯。
雙鍵 map - 超級實用
雙鍵的 map ,我突然感覺我發(fā)現(xiàn)了新大陸。比如我有一個業(yè)務(wù)場景是:根據(jù)職位和部門將公司人員區(qū)分開來。key 可以用職位 + 部門組成一個字符串,那我們有了雙鍵 map 之后就沒這種煩惱。
public static void main(String[] args) {
Table<String, String, List<Object>> tables = HashBasedTable.create();
tables.put("財務(wù)部", "總監(jiān)", Lists.newArrayList());
tables.put("財務(wù)部", "職員",Lists.newArrayList());
tables.put("法務(wù)部", "助理",Lists.newArrayList());
System.out.println(tables);
}
工具類
JDK里大家耳熟能詳?shù)氖?code>Collections 這個集合工具類, 提供了一些基礎(chǔ)的集合處理轉(zhuǎn)換功能, 但是實際使用里很多需求并不是簡單的排序, 或者比較數(shù)值大小, 然后 Guava 在此基礎(chǔ)上做了許多的改進優(yōu)化, 可以說是 Guava 最為成熟/流行的模塊之一。
- 數(shù)組相關(guān):Lists
- 集合相關(guān):Sets
- map 相關(guān):Maps
連接符(Joiner)和分隔符(Splitter)
Joiner 做為連接符的使用非常簡單,下例是將 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 的使用也很簡單:
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 的實現(xiàn)
Java 提供了 Comparator 可以用來對對象進行排序。Guava 提供了排序器 Ordering 類封裝了很多實用的操作。
Ordering 提供了一些有用的方法:
- natural() 對可排序類型做自然排序,如數(shù)字按大小,日期按先后排序
- usingToString() 按對象的字符串形式做字典排序[lexicographical ordering]
- from(Comparator) 把給定的Comparator轉(zhuǎn)化為排序器
- reverse() 獲取語義相反的排序器
- nullsFirst() 使用當前排序器,但額外把null值排到最前面。
- nullsLast() 使用當前排序器,但額外把null值排到最后面。
- compound(Comparator) 合成另一個比較器,以處理當前排序器中的相等情況。 lexicographical() 基于處理類型T的排序器,返回該類型的可迭代對象Iterable的排序器。
- onResultOf(Function) 對集合中元素調(diào)用Function,再按返回值用當前排序器排序。
示例:
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)計中間代碼運行時間
Stopwatch 類提供了時間統(tǒ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 在日常的使用中非常地頻繁,甚至都沒有意識到這是第三方提供的工具類而是把它當成了 JDK 自帶的實現(xiàn)。
// LoadingCache是Cache的緩存實現(xiàn)
LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
//設(shè)置緩存大小
.maximumSize(1000)
//設(shè)置到期時間
.expireAfterWrite(10, TimeUnit.MINUTES)
//設(shè)置緩存里的值兩分鐘刷新一次
.refreshAfterWrite(2, TimeUnit.MINUTES)
//開啟緩存的統(tǒng)計功能
.recordStats()
//構(gòu)建緩存
.build(new CacheLoader<String, Object>() {
//此處實現(xiàn)如果根據(jù)key找不到value需要去如何獲取
@Override
public Object load(String s) throws Exception {
return new Object();
}
//如果批量加載有比反復(fù)調(diào)用load更優(yōu)的方法則重寫這個方法
@Override
public Map<String, Object> loadAll(Iterable<? extends String> keys) throws Exception {
return super.loadAll(keys);
}
});
設(shè)置本地緩存使用 CacheBuilder.newBuilder(),支持設(shè)置緩存大小,緩存過期時間,緩存刷新頻率等等。如果你想統(tǒng)計緩存的命中率, Guava Cache 也提供了這種能力幫你匯總當前緩存是否有效。
同時緩存如果因為某種原因未自動刷新或者清除,Guava Cache 也支持用戶手動調(diào)用 API 刷新或者清除緩存。
cache.invalidateAll();//清除所有緩存項
//清理的時機:在寫操作時順帶做少量的維護工作,或者偶爾在讀操作時做——如果寫操作實在太少的話
//如果想自己維護則可以調(diào)用Cache.cleanUp();
cache.cleanUp();
//另外有時候需要緩存中的數(shù)據(jù)做出變化重載一次,這個過程可以異步執(zhí)行
cache.refresh("key");
單機限流工具類 - RateLimiter
常用的限流算法有 漏桶算法、令牌桶算法。這兩種算法各有側(cè)重點:
- 漏桶算法:漏桶的意思就像一個漏斗一樣,水一滴一滴的滴下去,流出是勻速的。當訪問量過大的時候這個漏斗就會積水。漏桶算法的實現(xiàn)依賴隊列,一個處理器從隊頭依照固定頻率取出數(shù)據(jù)進行處理。如果請求量過大導(dǎo)致隊列堆滿那么新來的請求就會被拋棄。漏桶一般按照固定的速率流出。
- 令牌桶則是存放固定容量的令牌,按照固定速率從桶中取出令牌。初始給桶中添加固定容量令牌,當桶中令牌不夠取出的時候則拒絕新的請求。令牌桶不限制取出令牌的速度,只要有令牌就能處理。所以令牌桶允許一定程度的突發(fā),而漏桶主要目的是平滑流出。
RateLimiter 使用了令牌桶算法,提供兩種限流的實現(xiàn)方案:
- 平滑突發(fā)限流(SmoothBursty)
- 平滑預(yù)熱限流(SmoothWarmingUp)
實現(xiàn)平滑突發(fā)限流通過 RateLimiter 提供的靜態(tà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 個,基本 0.2s 一次符合每秒 5 個的設(shè)置。保證每秒不超過 5 個達到了平滑輸出的效果。
在沒有請求使用令牌桶的時候,令牌會先創(chuàng)建好放在桶中,所以此時如果突然有突發(fā)流量進來,由于桶中有足夠的令牌可以快速響應(yīng)。RateLimiter 在沒有足夠令牌發(fā)放時采用滯后處理的方式,前一個請求獲取令牌所需等待的時間由下一次請求來承受。
平滑預(yù)熱限流并不會像平滑突發(fā)限流一樣先將所有的令牌創(chuàng)建好,它啟動后會有一段預(yù)熱期,逐步將分發(fā)頻率提升到配置的速率。
比如下面例子創(chuàng)建一個平均分發(fā)令牌速率為 2,預(yù)熱期為 3 分鐘。由于設(shè)置了預(yù)熱時間是 3 秒,令牌桶一開始并不會 0.5 秒發(fā)一個令牌,而是形成一個平滑線性下降的坡度,頻率越來越高,在 3 秒鐘之內(nèi)達到原本設(shè)置的頻率,以后就以固定的頻率輸出。這種功能適合系統(tǒ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
從上面的輸出看前面兩次獲取令牌都很耗時,往后就越來越趨于平穩(wěn)。
今天給大家介紹的常用的 Guava 工具類就這些,不過 JDK8 開始 Java官方 API 也在完善,比如像字符串相關(guān)的功能 JDK也很強大。都是工具,哪個好用就用哪個。
以上就是關(guān)于Google發(fā)布的Java核心工具包Guava的使用介紹就到此結(jié)束了,想要了解更多Java Guava工具包的其他內(nèi)容,請搜索W3Cschool以前的文章或繼續(xù)瀏覽下面的相關(guān)文章!