前言
平時我們都會封裝一些處理快取或其他的小工具。但每個人都封裝一次,重複造輪子,有點費時間。有沒有一些好的工具庫推薦-guava。guava是谷歌基於java封裝好的開源庫,它的效能、實用性,比我們自己造的輪子更好,畢竟谷歌出品,下面介紹下幾個常用的guava工具類
- LoadingCache(本地快取)
- Multimap 和 Multiset
- BiMap
- Table(表)
- Sets和Maps(交併差)
- EventBus(事件)
- StopWatch(秒錶)
- Files(檔案操作)
- RateLimiter(限流器)
- Guava Retry(重試)
關注公眾號,一起交流,微信搜一搜: 潛行前行
guava的maven配置引入
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0-jre</version>
</dependency>
LoadingCache
- LoadingCache 在實際場景中有著非常廣泛的使用,通常情況下如果遇到需要大量時間計算或者快取值的場景,就應當將值儲存到快取中。LoadingCache 和 ConcurrentMap 類似,但又不盡相同。最大的不同是 ConcurrentMap 會永久的儲存所有的元素值直到他們被顯示的移除,但是 LoadingCache 會為了保持記憶體使用合理會根據配置自動將過期值移除
- 通常情況下,Guava caching 適用於以下場景:
- 花費一些記憶體來換取速度
- 一些 key 會被不止一次被呼叫
- 快取內容有限,不會超過記憶體空間的值,Guava caches 不會儲存內容到檔案或者到伺服器外部,如果有此類需求考慮使用 Memcached, Redis
- LoadingCache 不能快取 null key
- CacheBuilder 構造 LoadingCache 引數介紹
CacheBuilder 方法引數 |
描述 |
initialCapacity(int initialCapacity) |
快取池的初始大小 |
concurrencyLevel(int concurrencyLevel) |
設定併發數 |
maximumSize(long maximumSize) |
快取池大小,在快取項接近該大小時, Guava開始回收舊的快取項 |
weakValues() |
設定value的儲存引用是虛引用 |
softValues() |
設定value的儲存引用是軟引用 |
expireAfterWrite(long duration, TimeUnit unit) |
設定時間物件沒有被寫則物件從記憶體中刪除(在另外的執行緒裡面不定期維護) |
expireAfterAccess(long duration, TimeUnit unit) |
設定時間物件沒有被讀/寫訪問則物件從記憶體中刪除(在另外的執行緒裡面不定期維護) |
refreshAfterWrite(long duration, TimeUnit unit) |
和expireAfterWrite類似,不過不立馬移除key,而是在下次更新時重新整理,這段時間可能會返回舊值 |
removalListener( RemovalListener<? super K1, ? super V1> listener) |
監聽器,快取項被移除時會觸發 |
build(CacheLoader<? super K1, V1> loader) |
當資料不存在時,則使用loader載入資料 |
- LoadingCache
V get(K key)
, 獲取快取值,如果鍵不存在值,將呼叫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);
Multimap 和 MultiSet
- Multimap的特點其實就是可以包含有幾個重複Key的value,可以put進入多個不同value但是相同的key,但是又不會覆蓋前面的內容
- 示例
//Multimap: key-value key可以重複,value也可重複
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 有一個相對有用的場景,就是跟蹤每種物件的數量,所以可以用來進行數量統計
- 示例
//MultiSet: 無序+可重複 count()方法獲取單詞的次數 增強了可讀性+操作簡單
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
BiMap
- BiMap的鍵必須唯一,值也必須唯一,可以實現value和key互轉
- 示例
BiMap<Integer,String> biMap = HashBiMap.create();
biMap.put(1,"lwl");
biMap.put(2,"csc");
BiMap<String, Integer> map = biMap.inverse(); // value和key互轉
map.forEach((v, k) -> System.out.println(v + "-" + k));
Table
Table<R,C,V> table = HashBasedTable.create();
,由泛型可以看出,table由雙主鍵R(行),C(列)共同決定,V是儲存值
- 新增資料:
table.put(R,C,V)
- 獲取資料:
V v = table.get(R,C)
- 遍歷資料:
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對應的value
System.out.println(tables.get("csc","lwl"));
Sets和Maps
// 不可變集合的建立
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());;
-------------結果-------------
{c=3}
{b=(2, 20)}
{a=1}
{d=4}
EventBus
- EventBus是Guava的事件處理機制,是設計模式中的觀察者模式(生產/消費者程式設計模型)的優雅實現。對於事件監聽和釋出訂閱模式
- EventBus內部實現原理不復雜,EventBus內部會維護一個Multimap<Class<?>, Subscriber> map,key就代表訊息對應的類(不同訊息不同類,區分不同的訊息)、value是一個Subscriber,Subscriber其實就是對應訊息處理者。如果有訊息釋出就去這個map裡面找到這個訊息對應的Subscriber去執行
- 使用示例
@Data
@AllArgsConstructor
public class OrderMessage {
String message;
}
//使用 @Subscribe 註解,表明使用dealWithEvent 方法處理 OrderMessage型別對應的訊息
//可以註解多個方法,不同的方法 處理不同的物件訊息
public class OrderEventListener {
@Subscribe
public void dealWithEvent(OrderMessage event) {
System.out.println("內容:" + event.getMessage());
}
}
-------------------------------------
// new AsyncEventBus(String identifier, Executor executor);
EventBus eventBus = new EventBus("lwl");
eventBus.register(new OrderEventListener());
// 釋出訊息
eventBus.post(new OrderMessage("csc"));
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);
Files檔案操作
File newFile = new File("D:/text.txt");
Files.write("this is a test".getBytes(), newFile);
//再次寫入會把之前的內容沖掉
Files.write("csc".getBytes(), newFile);
//追加寫
Files.append("lwl", newFile, Charset.defaultCharset());
File newFile = new File("E:/text.txt");
List<String> lines = Files.readLines(newFile, Charset.defaultCharset());
方法 |
描述 |
Files.copy(File from, File to) |
複製檔案 |
Files.deleteDirectoryContents(File directory) |
刪除資料夾下的內容(包括檔案與子資料夾) |
Files.deleteRecursively(File file) |
刪除檔案或者資料夾 |
Files.move(File from, File to) |
移動檔案 |
Files.touch(File file) |
建立或者更新檔案的時間戳 |
Files.getFileExtension(String file) |
獲得檔案的副檔名 |
Files.getNameWithoutExtension(String file) |
獲得不帶副檔名的檔名 |
Files.map(File file, MapMode mode) |
獲取記憶體對映buffer |
RateLimiter
//RateLimiter 構造方法,每秒限流permitsPerSecond
public static RateLimiter create(double permitsPerSecond)
//每秒限流 permitsPerSecond,warmupPeriod 則是資料初始預熱時間,從第一次acquire 或 tryAcquire 執行開時計算
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");
--------------- 結果 -------------------------
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,花了三秒時間去預熱資料,在第五次到第八次的acquire耗時趨於平滑
Guava Retry
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
RetryerBuilder方法 |
描述 |
withRetryListener |
重試監聽器 |
withWaitStrategy |
失敗後重試間隔時間 |
withStopStrategy |
停止策略 |
withBlockStrategy |
阻塞策略BlockStrategy |
withAttemptTimeLimiter |
執行時間限制策略 |
retryIfException |
發生異常,則重試 |
retryIfRuntimeException |
發生RuntimeException異常,則重試 |
retryIfExceptionOfType(Class<? extends Throwable> ex) |
發生ex異常,則重試 |
retryIfException(Predicate<Throwable> exceptionPredicate) |
對異常判斷,是否重試 |
retryIfResult(Predicate<V> resultPredicate) |
對返回結果判斷,是否重試 |
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfException()
.retryIfResult(Predicates.equalTo(false))
.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(1, TimeUnit.SECONDS))
.withStopStrategy(StopStrategies.stopAfterAttempt(5))
.build();
//Retryer呼叫
retryer.call(() -> true);
參考文章