Google Guava之--cache

一、簡介
  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就不可用了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章