一、簡介
Google Guava包含了Google的Java項目許多依賴的庫,如:集合 [collections] 、緩存 [caching] 、原生類型支持 [primitives support] 、並發庫 [concurrency libraries] 、通用注解 [common annotations] 、字符串處理 [string processing] 、I/O 等等。本文只介紹其中的緩存部分。
Guava Cache是一種本地緩存實現,支持多種緩存過期策略。性能好,簡單易用。緩存在很多場景下都是很有用的。如,通過key獲取一個value的花費的時間很多,而且獲取的次數不止一次的時候,就應該考慮使用緩存。Guava Cache與ConcurrentMap很相似,但也不完全一樣。最基本的區別是ConcurrentMap會一直保存所有添加的元素,直到顯式地移除。而Guava Cache為了限制內存占用,通常都設定為自動回收元素。在某些場景下,盡管LoadingCache 不回收元素,它也會自動加載緩存。
Guava Cache適用於以下應用場景:
- 系統的訪問速度首要考慮,而內存空間為次要考慮。
- 某些key對於的value會被查詢多次。
- 緩存中存放的數據總量不會超出內存的全部大小。
本文例子使用的guava 版本為guava-18.0.jar,下載地址如下:
http://central.maven.org/maven2/com/google/guava/guava/18.0/guava-18.0.jar
二、Cache使用方式
1、CacheLoader方式
代碼如下:
1 import java.util.ArrayList; 2 import java.util.List; 3 import java.util.concurrent.ExecutionException; 4 import java.util.concurrent.TimeUnit; 5 6 import org.junit.Test; 7 8 import com.google.cacahe.Person; 9 import com.google.common.cache.CacheBuilder; 10 import com.google.common.cache.CacheLoader; 11 import com.google.common.cache.LoadingCache; 12 13 public class TestGuavaCache { 14 15 @Test 16 public void testUserCacheLoader() throws ExecutionException { 17 // 模擬數據 18 final List<Person> list = new ArrayList<Person>(5); 19 list.add(new Person("1", "zhangsan")); 20 list.add(new Person("2", "lisi")); 21 list.add(new Person("3", "wangwu")); 22 23 // 創建cache 24 LoadingCache<String, Person> cache = CacheBuilder.newBuilder()// 25 .refreshAfterWrite(1, TimeUnit.MINUTES)// 給定時間內沒有被讀/寫訪問,則回收。 26 // .expireAfterWrite(5, TimeUnit.SECONDS)//給定時間內沒有寫訪問,則回收。 27 // .expireAfterAccess(3, TimeUnit.SECONDS)// 緩存過期時間為3秒 28 .maximumSize(100).// 設置緩存個數 29 build(new CacheLoader<String, Person>() { 30 @Override 31 /** 當本地緩存命沒有中時,調用load方法獲取結果並將結果緩存 32 */ 33 public Person load(String key) throws ExecutionException { 34 System.out.println(key + " load in cache"); 35 return getPerson(key); 36 } 37 38 // 此時一般我們會進行相關處理,如到數據庫去查詢 39 private Person getPerson(String key) throws ExecutionException { 40 System.out.println(key + " query"); 41 for (Person p : list) { 42 if (p.getId().equals(key)) 43 return p; 44 } 45 return null; 46 } 47 }); 48 49 cache.get("1"); 50 cache.get("2"); 51 cache.get("3"); 52 System.out.println("======= sencond time =========="); 53 cache.get("1"); 54 cache.get("2"); 55 cache.get("3"); 56 } 57 }
執行結果如下:
1 load in cache
1 query
2 load in cache
2 query
3 load in cache
3 query
======= sencond time ==========
第二次獲取的時候沒有執行獲取的方法,而是直接從緩存中獲取。
2、Callback方式
代碼如下:
1 import java.util.ArrayList; 2 import java.util.List; 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 6 import org.junit.Test; 7 8 import com.google.cacahe.Person; 9 import com.google.common.cache.Cache; 10 import com.google.common.cache.CacheBuilder; 11 12 public class TestGuavaCache { 13 14 15 @Test 16 public void testUserCallback() throws ExecutionException { 17 // 模擬數據 18 final List<Person> list = new ArrayList<Person>(5); 19 list.add(new Person("1", "zhangsan")); 20 list.add(new Person("2", "lisi")); 21 list.add(new Person("3", "wangwu")); 22 23 final String key = "1"; 24 Cache<String, Person> cache2 = CacheBuilder.newBuilder().maximumSize(1000).build(); 25 /** 26 * 用緩存中的get方法,當緩存命中時直接返回結果;否則,通過給定的Callable類call方法獲取結果並將結果緩存。<br/> 27 * 可以用一個cache對象緩存多種不同的數據,只需創建不同的Callable對象即可。 28 */ 29 Person person = cache2.get(key, new Callable<Person>() { 30 public Person call() throws ExecutionException { 31 System.out.println(key + " load in cache"); 32 return getPerson(key); 33 } 34 35 // 此時一般我們會進行相關處理,如到數據庫去查詢 36 private Person getPerson(String key) throws ExecutionException { 37 System.out.println(key + " query"); 38 for (Person p : list) { 39 if (p.getId().equals(key)) 40 return p; 41 } 42 return null; 43 } 44 }); 45 System.out.println("======= sencond time =========="); 46 person = cache2.getIfPresent(key); 47 person = cache2.getIfPresent(key); 48 } 49 }
執行結果如下:
1 load in cache
1 query
======= sencond time ==========
第二次獲取后也是直接從緩存中加載。
3、關於移除監聽器
通過CacheBuilder.removalListener(RemovalListener),我們可以聲明一個監聽器,從而可以在緩存被移除時做一些其他的操作。當緩存被移除時,RemovalListener會獲取移除bing通知[RemovalNotification],其中包含移除的key、value和RemovalCause。
示例代碼如下:
1 import java.util.concurrent.ExecutionException; 2 import java.util.concurrent.TimeUnit; 3 4 import org.junit.Test; 5 6 import com.google.cacahe.Person; 7 import com.google.common.cache.CacheBuilder; 8 import com.google.common.cache.CacheLoader; 9 import com.google.common.cache.LoadingCache; 10 import com.google.common.cache.RemovalListener; 11 import com.google.common.cache.RemovalNotification; 12 13 public class TestGuavaCache { 14 @Test 15 public void testListener() throws ExecutionException { 16 CacheLoader<String, Person> loader = new CacheLoader<String, Person>() { 17 @Override 18 // 當本地緩存命沒有中時,調用load方法獲取結果並將結果緩存 19 public Person load(String key) throws ExecutionException { 20 System.out.println(key + " load in cache"); 21 return getPerson(key); 22 } 23 // 此時一般我們會進行相關處理,如到數據庫去查詢 24 private Person getPerson(String key) throws ExecutionException { 25 System.out.println(key + " query"); 26 return new Person(key, "zhang" + key); 27 } 28 }; 29 30 // remove listener 31 RemovalListener<String, Person> removalListener = new RemovalListener<String, Person>() { 32 public void onRemoval(RemovalNotification<String, Person> removal) { 33 System.out.println("cause:" + removal.getCause() + " key:" + removal.getKey() + " value:" 34 + removal.getValue()); 35 } 36 }; 37 38 LoadingCache<String, Person> cache = CacheBuilder.newBuilder()// 39 .expireAfterWrite(2, TimeUnit.MINUTES).maximumSize(1024).removalListener(removalListener).build(loader); 40 cache.get("1");// 放入緩存 41 cache.get("1");// 第二次獲取(此時從緩存中獲取) 42 cache.invalidate("1");// 移除緩存 43 cache.get("1");// 重新獲取 44 cache.get("1");// 再次獲取(此時從緩存中獲取) 45 } 46 }
運行結果如下:
1 1 load in cache 2 1 query 3 cause:EXPLICIT key:1 value:Person [id=1, name=zhang1] 4 1 load in cache 5 1 query
三、其他相關方法
顯式插入:該方法可以直接向緩存中插入值,如果緩存中有相同key則之前的會被覆蓋。
cache.put(key, value);
顯式清除:我們也可以對緩存進行手動清除。
cache.invalidate(key); //單個清除 cache.invalidateAll(keys); //批量清除 cache.invalidateAll(); //清除所有緩存項
基於時間的移除:
expireAfterAccess(long, TimeUnit); 該鍵值對最后一次訪問后超過指定時間再移除 expireAfterWrite(long, TimeUnit) ;該鍵值對被創建或值被替換后超過指定時間再移除
基於大小的移除:指如果緩存的對象格式即將到達指定的大小,就會將不常用的鍵值對從cache中移除。
cacheBuilder.maximumSize(long)
size是指cache中緩存的對象個數。當緩存的個數開始接近size的時候系統就會進行移除的操作
緩存清除執行的時間
使用CacheBuilder構建的緩存不會"自動"執行清理和回收工作,也不會在某個緩存項過期后馬上清理,也沒有諸如此類的清理機制。它是在寫操作時順帶做少量的維護工作(清理);如果寫操作太少,讀操作的時候也會進行少量維護工作。因為如果要自動地持續清理緩存,就必須有一個線程,這個線程會和用戶操作競爭共享鎖。在某些環境下線程創建可能受限制,這樣CacheBuilder就不可用了。