工具篇:介紹幾個好用的guava工具類

潛行前行發表於2021-06-18

前言

平時我們都會封裝一些處理快取或其他的小工具。但每個人都封裝一次,重複造輪子,有點費時間。有沒有一些好的工具庫推薦-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

  • maven引入
<dependency>
  <groupId>com.github.rholder</groupId>
  <artifactId>guava-retrying</artifactId>
  <version>2.0.0</version>
</dependency>
  • RetryerBuilder 構造方法
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);

參考文章

相關文章