App下載

Java核心工具庫(kù)Guava的介紹及具體使用方法

指上菁蕪 2021-08-10 11:32:12 瀏覽數(shù) (7630)
反饋

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)文章!


1 人點(diǎn)贊