App下載

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

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

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


1 人點贊