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


前言

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

參考文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM