官方文檔: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
}
}
