Google Guava - Cache

1、簡介
  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 不回收元素,它也會自動加載緩存。java

  Guava Cache適用於如下應用場景:數據庫

  • 系統的訪問速度首要考慮,而內存空間爲次要考慮。
  • 某些key對於的value會被查詢屢次。
  • 緩存中存放的數據總量不會超出內存的所有大小。

本文例子使用的guava 版本爲guava-18.0.jar,下載地址以下:
http://central.maven.org/maven2/com/google/guava/guava/18.0/guava-18.0.jar緩存

2、Cache使用方式併發

  一、CacheLoader方式maven

  代碼以下:ide

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

import com.google.cacahe.Person;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

public class TestGuavaCache {

    @Test
    public void testUserCacheLoader() throws ExecutionException {
        // 模擬數據
        final List<Person> list = new ArrayList<Person>(5);
        list.add(new Person("1", "zhangsan"));
        list.add(new Person("2", "lisi"));
        list.add(new Person("3", "wangwu"));

        // 建立cache
        LoadingCache<String, Person> cache = CacheBuilder.newBuilder()//
                .refreshAfterWrite(1, TimeUnit.MINUTES)// 給定時間內沒有被讀/寫訪問,則回收。
                // .expireAfterWrite(5, TimeUnit.SECONDS)//給定時間內沒有寫訪問,則回收。
                // .expireAfterAccess(3, TimeUnit.SECONDS)// 緩存過時時間爲3秒
                .maximumSize(100).// 設置緩存個數
                build(new CacheLoader<String, Person>() {
                    @Override
                    /**  當本地緩存命沒有中時,調用load方法獲取結果並將結果緩存
                     */
                    public Person load(String key) throws ExecutionException {
                        System.out.println(key + " load in cache");
                        return getPerson(key);
                    }

                    // 此時通常咱們會進行相關處理,如到數據庫去查詢
                    private Person getPerson(String key) throws ExecutionException {
                        System.out.println(key + " query");
                        for (Person p : list) {
                            if (p.getId().equals(key))
                                return p;
                        }
                        return null;
                    }
                });

        cache.get("1");
        cache.get("2");
        cache.get("3");
        System.out.println("======= sencond time  ==========");
        cache.get("1");
        cache.get("2");
        cache.get("3");
    }
}

執行結果以下:性能

load in cache
query
load in cache
query
load in cache
query
======= sencond time  ==========

第二次獲取的時候沒有執行獲取的方法,而是直接從緩存中獲取。ui

  二、Callback方式google

  代碼以下:線程

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

import org.junit.Test;

import com.google.cacahe.Person;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

public class TestGuavaCache {
    

    @Test
    public void testUserCallback() throws ExecutionException {
        // 模擬數據
        final List<Person> list = new ArrayList<Person>(5);
        list.add(new Person("1", "zhangsan"));
        list.add(new Person("2", "lisi"));
        list.add(new Person("3", "wangwu"));

        final String key = "1";
        Cache<String, Person> cache2 = CacheBuilder.newBuilder().maximumSize(1000).build();
        /**
         * 用緩存中的get方法,當緩存命中時直接返回結果;不然,經過給定的Callable類call方法獲取結果並將結果緩存。<br/>
         * 能夠用一個cache對象緩存多種不一樣的數據,只需建立不一樣的Callable對象便可。
         */
        Person person = cache2.get(key, new Callable<Person>() {
            public Person call() throws ExecutionException {
                System.out.println(key + " load in cache");
                return getPerson(key);
            }

            // 此時通常咱們會進行相關處理,如到數據庫去查詢
            private Person getPerson(String key) throws ExecutionException {
                System.out.println(key + " query");
                for (Person p : list) {
                    if (p.getId().equals(key))
                        return p;
                }
                return null;
            }
        });
        System.out.println("======= sencond time  ==========");
        person = cache2.getIfPresent(key);
        person = cache2.getIfPresent(key);
    }
}

執行結果以下:

1 load in cache
1 query
======= sencond time  ==========

第二次獲取後也是直接從緩存中加載。

  三、關於移除監聽器

  經過CacheBuilder.removalListener(RemovalListener),咱們能夠聲明一個監聽器,從而能夠在緩存被移除時作一些其餘的操做。當緩存被移除時,RemovalListener會獲取移除bing通知[RemovalNotification],其中包含移除的key、value和RemovalCause。

  示例代碼以下:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

import com.google.cacahe.Person;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;

public class TestGuavaCache {
    @Test
    public void testListener() throws ExecutionException {
        CacheLoader<String, Person> loader = new CacheLoader<String, Person>() {
            @Override
            // 當本地緩存命沒有中時,調用load方法獲取結果並將結果緩存
            public Person load(String key) throws ExecutionException {
                System.out.println(key + " load in cache");
                return getPerson(key);
            }
            // 此時通常咱們會進行相關處理,如到數據庫去查詢
            private Person getPerson(String key) throws ExecutionException {
                System.out.println(key + " query");
                return new Person(key, "zhang" + key);
            }
        };

        // remove listener
        RemovalListener<String, Person> removalListener = new RemovalListener<String, Person>() {
            public void onRemoval(RemovalNotification<String, Person> removal) {
                System.out.println("cause:" + removal.getCause() + " key:" + removal.getKey() + " value:"
                        + removal.getValue());
            }
        };

        LoadingCache<String, Person> cache = CacheBuilder.newBuilder()//
                .expireAfterWrite(2, TimeUnit.MINUTES).maximumSize(1024).removalListener(removalListener).build(loader);
        cache.get("1");// 放入緩存
        cache.get("1");// 第二次獲取(此時從緩存中獲取)
        cache.invalidate("1");// 移除緩存
        cache.get("1");// 從新獲取
        cache.get("1");// 再次獲取(此時從緩存中獲取)
    }
}

運行結果以下:

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

3、其餘相關方法

  顯式插入:該方法能夠直接向緩存中插入值,若是緩存中有相同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就不可用了。

相關文章
相關標籤/搜索