Guava Cache 本地緩存組件淺析

cache組件中核心的類和接口列舉以下:
接口:java

  • Cache 本地緩存的頂級接口,提供一些對緩存進行get,put的方法,以及獲取緩存統計數據的方法等。
  • LoadingCache 繼承了Cache接口,並另外提供了一些當get數據不存在時自動去load相關key(s)所對應的value(s)的契約(即接口中的抽象方法),具體實現見LoadingCache的具體實現類。
  • RemovalListener 監聽器接口,在緩存被移除的時候用來作一些操做,與下面的RemovalNotification、RemovalCause配套使用。很明顯這是個觀察者模式的應用。
  • Weigher 權重的接口,提供int weigh(K key, V value)抽象方法,給緩存中的Entry賦予權重信息。

抽象類:算法

  • AbstractCache 自己是抽象類,實現自Cache接口,基本沒作什麼實際的工做,大多數方法的實現只是簡單拋出UnsupportedOperationException.該抽象類提供了Cache接口的骨架,爲了不子類直接繼承Cache接口時必須實現全部抽象方法,這種手法在其餘地方也很常見,我的以爲都算得上是一種設計模式了。
  • AbstractLoadingCache 繼承自AbstractCache並實現了LoadingCache接口,目的也是提供一個骨架,其中的某些方法提供了在get不到數據時會自動Load數據的契約。
  • CacheLoader 抽象類,最核心的方法就是load,封裝load數據的操做,具體如何laod與該抽象類的具體子類有關,只須要重寫laod方法,就能夠在get不到數據時自動去load數據到緩存中。
  • ForwardingCache 裝飾器模式的用法,全部對緩存的操做都委託給其餘的實現了Cache接口的類,該抽象類中有一個抽象方法protected abstract Cache<K, V> delegate();不難推測出來,其餘的方法中均使用了該代理。即相似get(key){delegate().get(key)}
  • ForwardingLoadingCache 自行推斷,不解釋。

類:設計模式

  • CacheBuilder 建造者模式的應用,經過該類來組裝Cache,最後調用build方法來生成Cache的實例
  • CacheBuilderSpec 用來構建CacheBuilder的實例,其中提供了一些配置參數,用這些配置的參數來經過CacheBuilder實例最終構建Cache實例。
  • CacheStats 緩存使用狀況統計信息,好比命中多少次,缺失多少次等等。
  • LocalCache 本地緩存最核心的類,Cache接口實例的代理人,Cache接口提供的一些方法內部均採委託給LocalCache實例來實現,LocalCache的具體實現相似於ConcurrentHashMap,也採用了分段的方式。
  • RemovalListeners 該類的文檔中說的是A collection of common removal listeners.感受這個類並非這個做用,這個類提供了一個asynchronous(RemovalListener, Executor)的方法,代碼以下:緩存

    public static <K, V> RemovalListener<K, V> asynchronous( final RemovalListener<K, V> listener, final Executor executor) { checkNotNull(listener); checkNotNull(executor); return new RemovalListener<K, V>() { @Override public void onRemoval(final RemovalNotification<K, V> notification) { executor.execute( new Runnable() { @Override public void run() { listener.onRemoval(notification); } }); } }; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    看這意思是將監聽器轉成異步執行的,也就是在移除緩存中的數據時,用異步的方法執行onRemoval操做,在onRemoval比較耗時的時候會提高性能,不至於阻塞對緩存的其餘操做。markdown

  • RemovalNotification 封裝了RemovalCause,典型的觀察者模式。

枚舉類:網絡

  • RemovalCause 引發移除緣由的枚舉類,如顯示調用invalidate方法產生的移除,或者是調用replace方法時產生的移除,或者是垃圾回收致使的移除,或者是緩存過時致使的移除,或者是超過了緩存大小限制致使的移除等等。

下面列出Cache組件的特色:
- 自動加載Entry(key-value對)到緩存中
- 當緩存超過設定的最大大小時採用LRU算法進行緩存的剔除
- 可設定緩存過時時間,基於最後一次訪問或者最後一次寫入緩存兩種方式
- keys自動使用WeakReference進行包裹
- values自動使用WeakReference或者SoftReference進行包裹
- 當Entry從緩存中剔除時會有通知機制
- 可以對緩存的使用狀況進行統計。異步

使用示例:async

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumSize(10000) .expireAfterWrite(10, TimeUnit.MINUTES) .removalListener(MY_LISTENER) .build( new CacheLoader<Key, Graph>() { public Graph load(Key key) throws AnyException { return createExpensiveGraph(key); } });}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

首先調用newBuilder靜態方法產生CacheBuilder對象,而後開始裝配。maximumSize(10000)表示緩存最多存放10000個鍵值對,超過這個數會利用LRU算法進行剔除,expireAfterWrite(10, TimeUnit.MINUTES)表示在key-value對寫入緩存以後10分鐘過時(過時數據不會馬上進行清除,而是在下一次進行get操做的時候判斷是否過時,若過時則剔除)。removalListener(MY_LISTENER)添加監聽器,在進行剔除操做的時候會調用該監聽器的onRemoval方法進行操做。MY_LISTENER表明實現了RemovalListener接口的子類對象。build()方法能夠傳入一個CacheLoader類的子類對象,該對象用來在get數據失敗時進行數據的load操做,load操做的過程就在重寫的load方法中。ide

注:若是不須要自動裝載數據的功能,能夠在最後的build()方法中不穿遞任何參數。帶不帶CacheLoad類型參數的build方法代碼以下所示:性能

public <K1 extends K, V1 extends V> Cache<K1, V1> build() { checkWeightWithWeigher(); checkNonLoadingCache(); return new LocalCache.LocalManualCache<K1, V1>(this); } public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build( CacheLoader<? super K1, V1> loader) { checkWeightWithWeigher(); return new LocalCache.LocalLoadingCache<K1, V1>(this, loader); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

能夠看出來它們返回的Cache實例類型分別是LocalCache.LocalManualCache和LocalCache.LocalLoadingCache這兩個靜態類。再去LocalCache類裏面稍微看一下這兩個靜態內部類的繼承層次。

static class LocalManualCache<K, V> implements Cache<K, V>, Serializable static class LocalLoadingCache<K, V> extends LocalManualCache<K, V> implements LoadingCache<K, V> 
  • 1
  • 2
  • 3
  • 4
  • 5

能夠看到LocalLoadingCache在繼承自LocalManualCache的基礎上還實現了LoadingCache接口。也就是說該類的一些方法中涉及到自動加載數據到緩存中的功能。所以構建哪一種Cache徹底取決於本身的需求。

最後須要提出的是,還能夠重寫CacheLoader中的loadAll(Iterable keys)方法,該方法能夠用來批量加載數據,在哪一種場景下須要這個方法呢?舉一個例子,好比根據key獲取value的操做須要通過網絡鏈接,比較耗時,則批量導入數據則能夠大大節省時間,也就是發一次網絡請求獲取一批數據回來。若是重寫了loadAll,須要利用批量加載數據,那麼就須要相應地調用Cache實例的getAll(Iterable keys)方法進行數據的批量獲取,批量獲取的過程當中,如有一批key對應的value沒有在緩存中,則會調用該loadAll方法進行批量加載。若沒有重寫loadAll方法,則會依次調用load方法去進行加載,所以是否須要重寫loadAll方法能夠看是否批量加載能大大節省時間。
使用示例以下:

LoadingCache<Key, Graph> loadingCache= CacheBuilder.newBuilder()
       .maximumSize(10000) .expireAfterWrite(10, TimeUnit.MINUTES) .removalListener(MY_LISTENER) .build( new CacheLoader<Key, Graph>() { public Graph load(Key key) throws AnyException { return createExpensiveGraph(key); } public Graph loadAll(Iterable<Key> keys) { return createExpensiveGraphs(keys); } });} //用法: Map<Key, Graph> mygraphs = loadingCache.getAll(keys);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

若須要的數據不在緩存中或者已過時,若是重寫了loadAll方法,則getAll方法內部會去調用loadAll方法加載須要的數據到緩存中,若是沒有重寫loadAll方法,getAll內部會依次調用load方法進行數據的加載。見以下代碼:

try { Map<K, V> newEntries = loadAll(keysToLoad, defaultLoader);//該laodAll方法會在defaultLoader.loadAll()方法沒有進行重寫時拋出異常,被下面的catch捕獲。(由於默認的loadAll重寫的邏輯就是是簡單地拋出一個異常。) for (K key : keysToLoad) { V value = newEntries.get(key); if (value == null) { throw new InvalidCacheLoadException("loadAll failed to return a value for " + key); } result.put(key, value); } } catch (UnsupportedLoadingOperationException e) { // loadAll not implemented, fallback to load for (K key : keysToLoad) { misses--; // get will count this miss result.put(key, get(key, defaultLoader));//捕獲到異常說明沒有重寫loadAll方法,則在get方法中會依次調用defaultLoader的load方法進行載入 } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

未完待續。。。。 若有不當之處還望指正!

相關文章
相關標籤/搜索