工具篇:介紹幾個好用的guava工具類
1 前言
平時我們都會封裝一些處理緩存或其他的小工具。但每個人都封裝一次,重復(fù)造輪子,有點費時間。有沒有一些好的工具庫推薦-guava。guava是谷歌基于java封裝好的開源庫,它的性能、實用性,比我們自己造的輪子更好,畢竟谷歌出品,下面介紹下幾個常用的guava工具類
LoadingCache(本地緩存)
Multimap 和 Multiset
BiMap
Table(表)
Sets和Maps(交并差)
EventBus(事件)
StopWatch(秒表)
Files(文件操作)
RateLimiter(限流器)
Guava Retry(重試)
2 guava的maven配置引入
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0-jre</version>
</dependency>
3 LoadingCache
LoadingCache 在實際場景中有著非常廣泛的使用,通常情況下如果遇到需要大量時間計算或者緩存值的場景,就應(yīng)當(dāng)將值保存到緩存中。LoadingCache 和 ConcurrentMap 類似,但又不盡相同。最大的不同是 ConcurrentMap 會永久的存儲所有的元素值直到他們被顯示的移除,但是 LoadingCache 會為了保持內(nèi)存使用合理會根據(jù)配置自動將過期值移除
通常情況下,Guava caching 適用于以下場景:
花費一些內(nèi)存來換取速度
一些 key 會被不止一次被調(diào)用
緩存內(nèi)容有限,不會超過內(nèi)存空間的值,Guava caches 不會存儲內(nèi)容到文件或者到服務(wù)器外部,如果有此類需求考慮使用 Memcached, Redis
LoadingCache 不能緩存 null key
CacheBuilder 構(gòu)造 LoadingCache 參數(shù)介紹

LoadingCache
V get(K key), 獲取緩存值,如果鍵不存在值,將調(diào)用CacheLoader的load方法加載新值到該鍵中示例
LoadingCache<Integer,Long> cacheMap = CacheBuilder.newBuilder().initialCapacity(10)
.concurrencyLevel(10)
.expireAfterAccess(Duration.ofSeconds(10))
.weakValues()
.recordStats()
.removalListener(new RemovalListener<Integer,Long>(){
@Override
public void onRemoval(RemovalNotification<Integer, Long> notification) {
System.out.println(notification.getValue());
}
})
.build(new CacheLoader<Integer,Long>(){
@Override
public Long load(Integer key) throws Exception {
return System.currentTimeMillis();
}
});
cacheMap.get(1);
4 Multimap 和 MultiSet
Multimap的特點其實就是可以包含有幾個重復(fù)Key的value,可以put進(jìn)入多個不同value但是相同的key,但是又不會覆蓋前面的內(nèi)容
示例
//Multimap: key-value key可以重復(fù),value也可重復(fù)
Multimap<String, String> multimap = ArrayListMultimap.create();
multimap.put("csc","1");
multimap.put("lwl","1");
multimap.put("csc","1");
multimap.put("lwl","one");
System.out.println(multimap.get("csc"));
System.out.println(multimap.get("lwl"));
---------------------------
[1, 1]
[1, one]
MultiSet 有一個相對有用的場景,就是跟蹤每種對象的數(shù)量,所以可以用來進(jìn)行數(shù)量統(tǒng)計
示例
//MultiSet: 無序+可重復(fù) count()方法獲取單詞的次數(shù) 增強了可讀性+操作簡單
Multiset<String> set = HashMultiset.create();
set.add("csc");
set.add("lwl");
set.add("csc");
System.out.println(set.size());
System.out.println(set.count("csc"));
---------------------------
3
2
5 BiMap
BiMap的鍵必須唯一,值也必須唯一,可以實現(xiàn)value和key互轉(zhuǎn)
示例
BiMap<Integer,String> biMap = HashBiMap.create();
biMap.put(1,"lwl");
biMap.put(2,"csc");
BiMap<String, Integer> map = biMap.inverse(); // value和key互轉(zhuǎn)
map.forEach((v, k) -> System.out.println(v + "-" + k));
6 Table
Table<R,C,V> table = HashBasedTable.create();,由泛型可以看出,table由雙主鍵R(行),C(列)共同決定,V是存儲值新增數(shù)據(jù):
table.put(R,C,V)獲取數(shù)據(jù):
V v = table.get(R,C)遍歷數(shù)據(jù):
Set<R> set = table.rowKeySet(); Set<C> set = table.columnKeySet();示例
// 雙鍵的Map Map--> Table-->rowKey+columnKey+value
Table<String, String, Integer> tables = HashBasedTable.create();
tables.put("csc", "lwl", 1);
//row+column對應(yīng)的value
System.out.println(tables.get("csc","lwl"));
7 Sets和Maps
// 不可變集合的創(chuàng)建
ImmutableList<String> iList = ImmutableList.of("csc", "lwl");
ImmutableSet<String> iSet = ImmutableSet.of("csc", "lwl");
ImmutableMap<String, String> iMap = ImmutableMap.of("csc", "hello", "lwl", "world");
set的交集, 并集, 差集
HashSet setA = newHashSet(1, 2, 3, 4, 5);
HashSet setB = newHashSet(4, 5, 6, 7, 8);
//并集
SetView union = Sets.union(setA, setB);
//差集 setA-setB
SetView difference = Sets.difference(setA, setB);
//交集
SetView intersection = Sets.intersection(setA, setB);
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> mapDifference = Maps.difference(mapA, mapB);
//mapA 和 mapB 相同的 entry
System.out.println(mapDifference.entriesInCommon());
//mapA 和 mapB key相同的value不同的 entry
System.out.println(mapDifference.entriesDiffering());
//只存在 mapA 的 entry
System.out.println(mapDifference.entriesOnlyOnLeft());
//只存在 mapB 的 entry
System.out.println(mapDifference.entriesOnlyOnRight());;
-------------結(jié)果-------------
{c=3}
{b=(2, 20)}
{a=1}
{d=4}
8 EventBus
EventBus是Guava的事件處理機制,是設(shè)計模式中的觀察者模式(生產(chǎn)/消費者編程模型)的優(yōu)雅實現(xiàn)。對于事件監(jiān)聽和發(fā)布訂閱模式
EventBus內(nèi)部實現(xiàn)原理不復(fù)雜,EventBus內(nèi)部會維護(hù)一個Multimap<Class<?>, Subscriber> map,key就代表消息對應(yīng)的類(不同消息不同類,區(qū)分不同的消息)、value是一個Subscriber,Subscriber其實就是對應(yīng)消息處理者。如果有消息發(fā)布就去這個map里面找到這個消息對應(yīng)的Subscriber去執(zhí)行
使用示例
@Data
@AllArgsConstructor
public class OrderMessage {
String message;
}
//使用 @Subscribe 注解,表明使用dealWithEvent 方法處理 OrderMessage類型對應(yīng)的消息
//可以注解多個方法,不同的方法 處理不同的對象消息
public class OrderEventListener {
@Subscribe
public void dealWithEvent(OrderMessage event) {
System.out.println("內(nèi)容:" + event.getMessage());
}
}
-------------------------------------
// new AsyncEventBus(String identifier, Executor executor);
EventBus eventBus = new EventBus("lwl");
eventBus.register(new OrderEventListener());
// 發(fā)布消息
eventBus.post(new OrderMessage("csc"));
9 StopWatch
Stopwatch stopwatch = Stopwatch.createStarted();
for(int i=0; i<100000; i++){
// do some thing
}
long nanos = stopwatch.elapsed(TimeUnit.MILLISECONDS);
System.out.println("邏輯代碼運行耗時:"+nanos);
10 Files文件操作
數(shù)據(jù)寫入
File newFile = new File("D:/text.txt");
Files.write("this is a test".getBytes(), newFile);
//再次寫入會把之前的內(nèi)容沖掉
Files.write("csc".getBytes(), newFile);
//追加寫
Files.append("lwl", newFile, Charset.defaultCharset());
文本數(shù)據(jù)讀取
File newFile = new File("E:/text.txt");
List<String> lines = Files.readLines(newFile, Charset.defaultCharset());
其他操作

11 RateLimiter
//RateLimiter 構(gòu)造方法,每秒限流permitsPerSecond
public static RateLimiter create(double permitsPerSecond)
//每秒限流 permitsPerSecond,warmupPeriod 則是數(shù)據(jù)初始預(yù)熱時間,從第一次acquire 或 tryAcquire 執(zhí)行開時計算
public static RateLimiter create(double permitsPerSecond, Duration warmupPeriod)
//獲取一個令牌,阻塞,返回阻塞時間
public double acquire()
//獲取 permits 個令牌,阻塞,返回阻塞時間
public double acquire(int permits)
//獲取一個令牌,超時返回
public boolean tryAcquire(Duration timeout)
////獲取 permits 個令牌,超時返回
public boolean tryAcquire(int permits, Duration timeout)
使用示例
RateLimiter limiter = RateLimiter.create(2, 3, TimeUnit.SECONDS);
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
--------------- 結(jié)果 -------------------------
get one permit cost time: 0.0s
get one permit cost time: 1.331672s
get one permit cost time: 0.998392s
get one permit cost time: 0.666014s
get one permit cost time: 0.498514s
get one permit cost time: 0.498918s
get one permit cost time: 0.499151s
get one permit cost time: 0.488548s
因為RateLimiter滯后處理的,所以第一次無論取多少都是零秒
可以看到前四次的acquire,花了三秒時間去預(yù)熱數(shù)據(jù),在第五次到第八次的acquire耗時趨于平滑
12 Guava Retry
maven引入
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
RetryerBuilder 構(gòu)造方法

Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfException()
.retryIfResult(Predicates.equalTo(false))
.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(1, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(5))
.build();
//Retryer調(diào)用
retryer.call(() -> true);
spring也有對應(yīng)的重試機制,相關(guān)文章可以看看重試框架Guava-Retry和spring-Retry[1]歡迎指正文中錯誤(故事純屬虛構(gòu),如有雷同純屬巧合)
13 參考文章
Google guava工具類的介紹和使用[2] 重試框架Guava-Retry和spring-Retry[3] 超詳細(xì)的Guava RateLimiter限流原理解析[4]
參考資料
重試框架Guava-Retry和spring-Retry: https://blog.csdn.net/zzzgd_666/article/details/84377962
[2]Google guava工具類的介紹和使用: https://blog.csdn.net/wwwdc1012/article/details/82228458
[3]重試框架Guava-Retry和spring-Retry: https://blog.csdn.net/zzzgd_666/article/details/84377962
[4]超詳細(xì)的Guava RateLimiter限流原理解析: https://zhuanlan.zhihu.com/p/60979444
有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號
好文章,我在看??
