官方文檔:https://github.com/google/guava/wiki/CachesExplained
目錄
二、快速入門
2.1、引入依賴
2.2、第一個示例
2.3、批量操作
三、拓展
3.1、移除監聽器
3.2、刷新緩存
3.3、自定義刷新的操作
一、guava cache介紹
對於常見的cache(緩存),比如memecache、redis、tair這些中間件,也有jdk的一些類庫可以當做緩存使用,比如實現了ConcurrentMap接口的ConcurrentHashMap。
ConcurrentHashMap雖然可以讓我們在程序中緩存一些數據,但是這些數據其實永遠不會過期,除非手動刪除,否則數據將一直存在於內存中。
而guava cache(LocalCache,本地緩存),也是實現了ConcurrentMap的類,但他和ConcurrentHashMap的區別在於,guava cache可以設置數據過期以及淘汰機制,更加符合一般的應用需求。
guava cache,是一個本地緩存,也就是說,他的數據是存放在機器內存,這個機器,就是JVM所在的機器(會占用一部分內存),而不是像redis那樣專門做緩存的機器。
二、快速入門
2.1、引入依賴
guava cahce是guava的子模塊。
guava倉庫地址:https://mvnrepository.com/artifact/com.google.guava/guava
<!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.0-jre</version> </dependency>
2.2、第一個示例
下面是一個簡單的示例:
package cn.ganlixin.guava; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import org.junit.Test; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class UseCache { @Test public void testLoadingCache() throws ExecutionException { // 創建緩存(容器) LoadingCache<String, String> cache = CacheBuilder.newBuilder() // 數據寫入1分鍾后失效 .expireAfterWrite(1, TimeUnit.MINUTES) // 支持緩存項的數據最大為3個 .maximumSize(3) // 實現CacheLoader,當在緩存中未找到所需的緩存項時,會執行CacheLoader的load方法加載緩存。 .build(new CacheLoader<String, String>() { // 方法的返回值會被緩存,注意,返回null時,會拋異常 @Override public String load(String key) throws Exception { System.out.println("key:" + key + " 未找到,開始加載...."); return key + "-" + key; } }); // 加入緩存 cache.put("Java", "spring"); cache.put("PHP", "swoole"); // 從緩存中獲取值 String res1 = cache.get("Java"); // get方法需要拋出ExecutionException異常 // cache.getUnchecked("Java"); // 功能和get一樣,但是不會throw異常 System.out.println(res1); // 輸出:spring // 緩存中為存放key為Golang的緩存項,所以進行加載 String res2 = cache.get("Golang"); System.out.println(res2); // key:Golang 未找到,開始加載.... // Golang-Golang // Golang的緩存項前面已經加載過了,且沒有被清除,所以這次直接獲取到對應的value String res3 = cache.get("Golang"); System.out.println(res3); // Golang-Golang // 前面添加了3個緩存項(已經達到設置上限,此時再添加緩存項,將會觸發淘汰) cache.put("Node", "KOA"); System.out.println(cache.get("PHP")); // PHP在cache中已被淘汰 // key:PHP 未找到,開始加載.... // PHP-PHP // 查詢緩存,如果未找到,不會觸發加載操作,而是范圍null。 String res4 = cache.getIfPresent("key"); System.out.println(res4); // null } }
2.3、批量操作
guava cache支持批量添加、查詢、清除操作。
package cn.ganlixin.guava; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; import org.junit.Test; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class UseCache { @Test public void testMultiple() throws ExecutionException { // 創建緩存 LoadingCache<String, String> loadingCache = CacheBuilder.newBuilder() .maximumSize(2) // 最大緩存項數量為2 .expireAfterWrite(10, TimeUnit.SECONDS) // 數據寫入10秒過期 .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { System.out.println("key:" + key + " 未找到,開始加載...."); return key + "-" + key; } }); Map<String, String> map = new HashMap<>(); map.put("one", "111111"); map.put("two", "222222"); map.put("three", "3333333"); // 批量添加 loadingCache.putAll(map); List<String> keys = new ArrayList<>(); keys.add("one"); keys.add("two"); // 批量獲取 ImmutableMap<String, String> allData = loadingCache.getAll(keys); System.out.println(allData); // {one=one-one, two=222222} // 批量清除 loadingCache.invalidateAll(keys); loadingCache.invalidateAll(); // 全量清除 } }
三、拓展
3.1、移除監聽器
移除監聽器,是指監聽緩存項,當緩存項被清除時,執行指定對操作。
package cn.ganlixin.guava; import com.google.common.cache.*; import org.junit.Test; import java.util.concurrent.TimeUnit; public class UseCache { @Test public void testRemoveListender() { LoadingCache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(2) .expireAfterWrite(1, TimeUnit.MINUTES) // 綁定"移除監聽器",當元素被清除時,執行onRemoval方法 .removalListener(new RemovalListener<String, String>() { @Override public void onRemoval(RemovalNotification removalNotification) { Object key = removalNotification.getKey(); Object val = removalNotification.getValue(); System.out.println("被清除的元素,key:" + key + ", val:" + val); } }) // 當在緩存中未找到key時,執行load操作。 .build(new CacheLoader<String, String>() { // 當key未找到時,執行的加載操作 @Override public String load(String key) throws Exception { System.out.println("未找到key:" + key + ",開始加載..."); return key + key; } }); cache.put("one", "111111"); cache.put("two", "123456"); // 繼續添加元素,會觸發清理操作,觸發移除監聽器 cache.put("three", "2233333"); // 輸出:被清除的元素,key:one, val:111111 String res = cache.getUnchecked("four"); System.out.println(res); // 輸出 // 未找到key:four,開始加載... // 被清除的元素,key:two, val:123456 // fourfour } }
3.2、刷新緩存
刷新緩存,是指一個緩存項寫入緩存后,經過一段時間(設置的刷新間隔),再次訪問該緩存項的時候,會刷新該緩存項,可以理解為將該項的key取出,執行CacheLoader的load方法,然后將返回值替換舊的值。
可以在創建緩存的時候,設置刷新緩存的時間:
package cn.ganlixin.guava; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import org.junit.Test; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class UseCache { @Test public void testRefresh() throws InterruptedException, ExecutionException { LoadingCache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(2) // 設置刷新的時機 .refreshAfterWrite(2, TimeUnit.SECONDS) .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { System.out.println("key:" + key + " 未找到,開始加載...."); return key + "-" + key; } }); cache.put("one", "11111"); cache.put("two", "22222"); // 休眠3秒 Thread.sleep(3000L); System.out.println(cache.get("one")); //key:one 未找到,開始加載.... //one-one System.out.println(cache.get("two")); //key:two 未找到,開始加載.... //two-two } }
需要注意的是,以上面代碼為例,並不是設置寫入2秒后,就會被刷新,而是當寫入2秒后,且再次被訪問時,才會被刷新;如果一個緩存項寫入的時間超過2秒,但是一直沒有訪問該項,那么這一項是不會被刷新的。這與Memecache和Redis的原理類似。
3.3、自定義刷新緩存的操作
前面內容中,提到可以使用設置refreshAfterWrite()設置數據寫入多久后,再次被訪問時,會被刷新,其實,我們可以對於刷新操作自定義,只需要重寫CacheLoader的reload方法即可。
package cn.ganlixin.guava; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.util.concurrent.ListenableFuture; import org.junit.Test; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class UseCache { @Test public void testReload() throws InterruptedException { LoadingCache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(2) // 設置刷新的時機 .refreshAfterWrite(2, TimeUnit.SECONDS) .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { System.out.println("key:" + key + " 未找到,開始加載...."); return key + "-" + key; } // 刷新緩存時,執行的操作 @Override public ListenableFuture<String> reload(String key, String oldValue) throws Exception { System.out.println("刷新緩存項,key:" + key + ", oldValue:" + oldValue); return super.reload(key, oldValue); } }); cache.put("hello", "world"); Thread.sleep(3000L); System.out.println(cache.getUnchecked("hello")); // 刷新緩存項,key:hello, oldValue:world // key:hello 未找到,開始加載.... // hello-hello } }