官方文檔:https://github.com/google/guava/wiki/CachesExplainedjava
1、guava cache介紹git
2、快速入門github
2.一、引入依賴redis
2.二、第一個示例spring
2.三、批量操做緩存
3、拓展swoole
3.一、移除監聽器ide
3.二、刷新緩存ui
3.三、自定義刷新的操做google
對於常見的cache(緩存),好比memecache、redis、tair這些中間件,也有jdk的一些類庫能夠當作緩存使用,好比實現了ConcurrentMap接口的ConcurrentHashMap。
ConcurrentHashMap雖然可讓咱們在程序中緩存一些數據,可是這些數據其實永遠不會過時,除非手動刪除,不然數據將一直存在於內存中。
而guava cache(LocalCache,本地緩存),也是實現了ConcurrentMap的類,但他和ConcurrentHashMap的區別在於,guava cache能夠設置數據過時以及淘汰機制,更加符合通常的應用需求。
guava cache,是一個本地緩存,也就是說,他的數據是存放在機器內存,這個機器,就是JVM所在的機器(會佔用一部份內存),而不是像redis那樣專門作緩存的機器。
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>
下面是一個簡單的示例:
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 } }
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(); // 全量清除 } }
移除監聽器,是指監聽緩存項,當緩存項被清除時,執行指定對操做。
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 } }
刷新緩存,是指一個緩存項寫入緩存後,通過一段時間(設置的刷新間隔),再次訪問該緩存項的時候,會刷新該緩存項,能夠理解爲將該項的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的原理相似。
前面內容中,提到可使用設置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 } }