Guava Cache與ConcurrentMap很相似,但也不完全一樣。最基本的區別是ConcurrentMap會一直保存所有添加的元素,直到顯式地移除。相對地,Guava Cache爲了限制內存佔用,通常都設定爲自動回收元素。在某些場景下,儘管LoadingCache 不回收元素,它也是很有用的,因爲它會自動加載緩存。
Guava Cache是在內存中緩存數據,相比較於數據庫或redis存儲,訪問內存中的數據會更加高效。Guava官網介紹,下面的這幾種情況可以考慮使用Guava Cache:
(1)消耗一些內存空間來提升速度。
(2)預料到某些鍵會被多次查詢。
(3)緩存中存放的數據總量不會超出內存容量。
所以,可以將程序頻繁用到的少量數據存儲到Guava Cache中,以改善程序性能。下面對Guava Cache的用法進行詳細的介紹。
1. 接口方法
Gauva Cache代表一塊緩存,它有如下方法:
public interface Cache<K, V> {
V get(K key, Callable<? extends V> valueLoader) throws ExecutionException;
ImmutableMap<K, V> getAllPresent(Iterable<?> keys);
void put(K key, V value);
void putAll(Map<? extends K, ? extends V> m);
void invalidate(Object key);
void invalidateAll(Iterable<?> keys);
void invalidateAll();
long size();
CacheStats stats();
ConcurrentMap<K, V> asMap();
void cleanUp();
}
2.構建緩存對象
可以通過CacheBuilder類構建一個緩存對象,CacheBuilder類採用builder設計模式,它的每個方法都返回CacheBuilder本身,直到build方法被調用。構建一個緩存對象代碼如下。
public class GuavaCache {
public static void main(String[] args) {
Cache<String,String> cache = CacheBuilder.newBuilder().build();
cache.put("word","Hello Guava Cache");
System.out.println(cache.getIfPresent("word"));
}
}
上面的代碼通過CacheBuilder.newBuilder().build()這句代碼創建了一個Cache緩存對象,並在緩存對象中存儲了key爲word,value爲Hello Guava Cache的一條記錄。可以看到Cache非常類似於JDK中的Map,但是相比於Map,Guava Cache提供了很多更強大的功能。
3.設置最大存儲
Guava Cache可以在構建緩存對象時指定緩存所能夠存儲的最大記錄數量。當Cache中的記錄數量達到最大值後再調用put方法向其中添加對象,Guava會先從當前緩存的對象記錄中選擇一條刪除掉,騰出空間後再將新的對象存儲到Cache中。
public class GuavaCache {
public static void main(String[] args) {
Cache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(2)
.build();
cache.put("key1","value1");
cache.put("key2","value2");
cache.put("key3","value3");
System.out.println("第一個值:" + cache.getIfPresent("key1")); // null
System.out.println("第二個值:" + cache.getIfPresent("key2")); // value2
System.out.println("第三個值:" + cache.getIfPresent("key3")); // value3
}
}
上面代碼在構造緩存對象時,通過CacheBuilder類的maximumSize方法指定Cache最多可以存儲兩個對象,然後調用Cache的put方法向其中添加了三個對象。程序執行結果如下圖所示,可以看到第三條對象記錄的插入,導致了第一條對象記錄被刪除。
4.設置過期時間
在構建Cache對象時,可以通過CacheBuilder類的expireAfterAccess和expireAfterWrite兩個方法爲緩存中的對象指定過期時間,過期的對象將會被緩存自動刪除。其中,expireAfterWrite方法指定對象被寫入到緩存後多久過期,expireAfterAccess指定對象多久沒有被訪問後過期。
public class GuavaCache {
public static void main(String[] args) throws InterruptedException {
Cache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(2)
.expireAfterWrite(3,TimeUnit.SECONDS)
.build();
cache.put("key1","value1");
int time = 1;
while(true) {
System.out.println("第" + time++ + "次取到key1的值爲:" + cache.getIfPresent("key1"));
Thread.sleep(1000);
}
}
}
上面的代碼在構造Cache對象時,通過CacheBuilder的expireAfterWrite方法指定put到Cache中的對象在3秒後會過期。在Cache對象中存儲一條對象記錄後,每隔1秒讀取一次這條記錄。程序運行結果如下圖所示,可以看到,前三秒可以從Cache中獲取到對象,超過三秒後,對象從Cache中被自動刪除。
運行結果:
第1次取到key1的值爲:value1
第2次取到key1的值爲:value1
第3次取到key1的值爲:value1
第4次取到key1的值爲:null
第5次取到key1的值爲:null
第6次取到key1的值爲:null
第7次取到key1的值爲:null
第8次取到key1的值爲:null
第9次取到key1的值爲:null
...
public class GuavaCache {
public static void main(String[] args) throws InterruptedException {
Cache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(2)
.expireAfterAccess(3,TimeUnit.SECONDS)
.build();
cache.put("key1","value1");
int time = 1;
while(true) {
Thread.sleep(time*1000);
System.out.println("睡眠" + time++ + "秒後取到key1的值爲:" + cache.getIfPresent("key1"));
}
}
}
通過CacheBuilder的expireAfterAccess方法指定Cache中存儲的對象如果超過3秒沒有被訪問就會過期。while中的代碼每sleep一段時間就會訪問一次Cache中存儲的對象key1,每次訪問key1之後下次sleep的時間會加長一秒。程序運行結果如下圖所示,從結果中可以看出,當超過3秒沒有讀取key1對象之後,該對象會自動被Cache刪除。
運行結果:
睡眠1秒後取到key1的值爲:value1
睡眠2秒後取到key1的值爲:value1
睡眠3秒後取到key1的值爲:null
睡眠4秒後取到key1的值爲:null
睡眠5秒後取到key1的值爲:null
睡眠6秒後取到key1的值爲:null
睡眠7秒後取到key1的值爲:null
睡眠8秒後取到key1的值爲:null
睡眠9秒後取到key1的值爲:null
睡眠10秒後取到key1的值爲:null
睡眠11秒後取到key1的值爲:null
說明:可同時用expireAfterAccess和expireAfterWrite方法指定過期時間,這時只要對象滿足兩者中的一個條件就會被自動過期刪除。
5.顯示清除
可以調用Cache的invalidateAll或invalidate方法顯示刪除Cache中的記錄。invalidate方法一次只能刪除Cache中一個記錄,接收的參數是要刪除記錄的key。invalidateAll方法可以批量刪除Cache中的記錄,當沒有傳任何參數時,invalidateAll方法將清除Cache中的全部記錄。invalidateAll也可以接收一個Iterable類型的參數,參數中包含要刪除記錄的所有key值。下面代碼對此做了示例。
public class GuavaCache {
public static void main(String[] args) throws InterruptedException {
Cache<String,String> cache = CacheBuilder.newBuilder().build();
Object value = new Object();
cache.put("key1","value1");
cache.put("key2","value2");
cache.put("key3","value3");
List<String> list = new ArrayList<String>();
list.add("key1");
list.add("key2");
cache.invalidateAll(list);//批量清除list中全部key對應的記錄
System.out.println(cache.getIfPresent("key1")); // null
System.out.println(cache.getIfPresent("key2")); // null
System.out.println(cache.getIfPresent("key3")); // value3
}
}
代碼中構造了一個集合list用於保存要刪除記錄的key值,然後調用invalidateAll方法批量刪除key1和key2對應的記錄,只剩下key3對應的記錄沒有被刪除。
6. 弱引用
可以通過weakKeys和weakValues方法指定Cache只保存對緩存記錄key和value的弱引用。這樣當沒有其他強引用指向key和value時,key和value對象就會被垃圾回收器回收。
public class GuavaCache {
public static void main(String[] args) throws InterruptedException {
Cache<String,Object> cache = CacheBuilder.newBuilder()
.maximumSize(2)
.weakValues()
.build();
Object value = new Object();
cache.put("key1",value);
value = new Object();//原對象不再有強引用
System.gc();
System.out.println(cache.getIfPresent("key1")); //null
}
}
上面代碼的打印結果是null。構建Cache時通過weakValues方法指定Cache只保存記錄值的一個弱引用。當給value引用賦值一個新的對象之後,就不再有任何一個強引用指向原對象。System.gc()觸發垃圾回收後,原對象就被清除了。
7. 統計信息
可以對Cache的命中率、加載數據時間等信息進行統計。在構建Cache對象時,可以通過CacheBuilder的recordStats方法開啓統計信息的開關。開關開啓後Cache會自動對緩存的各種操作進行統計,調用Cache的stats方法可以查看統計後的信息。
public class GuavaCache {
public static void main(String[] args) throws InterruptedException {
Cache<String,String> cache = CacheBuilder.newBuilder()
.maximumSize(3)
.recordStats() //開啓統計信息開關
.build();
cache.put("key1","value1");
cache.put("key2","value2");
cache.put("key3","value3");
cache.put("key4","value4");
cache.getIfPresent("key1");
cache.getIfPresent("key2");
cache.getIfPresent("key3");
cache.getIfPresent("key4");
cache.getIfPresent("key5");
cache.getIfPresent("key6");
System.out.println(cache.stats()); //獲取統計信息
}
}
運行結果:
CacheStats{hitCount=3, missCount=3, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=1}