<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 3-緩存 [原文地址](http://code.google.com/p/guava-libraries/wiki/CachesExplained)? [譯文地址](http://ifeve.com/google-guava-cachesexplained/)??? 譯者:許巧輝 ?校對:沈義揚 ## 范例 ``` LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .removalListener(MY_LISTENER) .build( new CacheLoader<Key, Graph>() { public Graph load(Key key) throws AnyException { return createExpensiveGraph(key); } }); ``` ## 適用性 緩存在很多場景下都是相當有用的。例如,計算或檢索一個值的代價很高,并且對同樣的輸入需要不止一次獲取值的時候,就應當考慮使用緩存。 緩存在很多場景下都是相當有用的。例如,計算或檢索一個值的代價很高,并且對同樣的輸入需要不止一次獲取值的時候,就應當考慮使用緩存。 Guava Cache與ConcurrentMap很相似,但也不完全一樣。最基本的區別是ConcurrentMap會一直保存所有添加的元素,直到顯式地移除。相對地,Guava Cache為了限制內存占用,通常都設定為自動回收元素。在某些場景下,盡管LoadingCache 不回收元素,它也是很有用的,因為它會自動加載緩存。 通常來說,`Guava Cache`適用于: * 你愿意消耗一些內存空間來提升速度。 * 你預料到某些鍵會被查詢一次以上。 * 緩存中存放的數據總量不會超出內存容量。(Guava Cache是單個應用運行時的本地緩存。它不把數據存放到文件或外部服務器。如果這不符合你的需求,請嘗試[Memcached](http://memcached.org/)這類工具) 如果你的場景符合上述的每一條,Guava Cache就適合你。 如同范例代碼展示的一樣,Cache實例通過CacheBuilder生成器模式獲取,但是自定義你的緩存才是最有趣的部分。 _注_:如果你不需要Cache中的特性,使用ConcurrentHashMap有更好的內存效率——但Cache的大多數特性都很難基于舊有的ConcurrentMap復制,甚至根本不可能做到。 ## 加載 在使用緩存前,首先問自己一個問題:有沒有合理的默認方法來加載或計算與鍵關聯的值?如果有的話,你應當使用CacheLoader。如果沒有,或者你想要覆蓋默認的加載運算,同時保留"獲取緩存-如果沒有-則計算"[get-if-absent-compute]的原子語義,你應該在調用get時傳入一個Callable實例。緩存元素也可以通過Cache.put方法直接插入,但自動加載是首選的,因為它可以更容易地推斷所有緩存內容的一致性。 ### [CacheLoader](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheLoader.html) LoadingCache是附帶CacheLoader構建而成的緩存實現。創建自己的CacheLoader通常只需要簡單地實現V load(K key) throws Exception方法。例如,你可以用下面的代碼構建LoadingCache: ``` LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() .maximumSize(1000) .build( new CacheLoader<Key, Graph>() { public Graph load(Key key) throws AnyException { return createExpensiveGraph(key); } }); ... try { return graphs.get(key); } catch (ExecutionException e) { throw new OtherException(e.getCause()); } ``` 從LoadingCache查詢的正規方式是使用[get(K)](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/LoadingCache.html#get%28K%29)方法。這個方法要么返回已經緩存的值,要么使用CacheLoader向緩存原子地加載新值。由于CacheLoader可能拋出異常,LoadingCache.get(K)也聲明為拋出ExecutionException異常。如果你定義的CacheLoader沒有聲明任何檢查型異常,則可以通過getUnchecked(K)查找緩存;但必須注意,一旦CacheLoader聲明了檢查型異常,就不可以調用getUnchecked(K)。 ``` LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() .expireAfterAccess(10, TimeUnit.MINUTES) .build( new CacheLoader<Key, Graph>() { public Graph load(Key key) { // no checked exception return createExpensiveGraph(key); } }); ... return graphs.getUnchecked(key); ``` getAll(Iterable&lt;? extends K&gt;)方法用來執行批量查詢。默認情況下,對每個不在緩存中的鍵,getAll方法會單獨調用CacheLoader.load來加載緩存項。如果批量的加載比多個單獨加載更高效,你可以重載[CacheLoader.loadAll](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheLoader.html#loadAll%28java.lang.Iterable%29)來利用這一點。getAll(Iterable)的性能也會相應提升。 _注:CacheLoader.loadAll的實現可以為沒有明確請求的鍵加載緩存值。例如,為某組中的任意鍵計算值時,能夠獲取該組中的所有鍵值,loadAll方法就可以實現為在同一時間獲取該組的其他鍵值_。_校注:getAll(Iterable&lt;? extends K&gt;)方法會調用loadAll,但會篩選結果,只會返回請求的鍵值對。_ ### [`Callable`](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Callable.html) 所有類型的Guava Cache,不管有沒有自動加載功能,都支持[get(K, Callable&lt;V&gt;)](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/Cache.html#get%28java.lang.Object,java.util.concurrent.Callable%29)方法。這個方法返回緩存中相應的值,或者用給定的Callable運算并把結果加入到緩存中。在整個加載方法完成前,緩存項相關的可觀察狀態都不會更改。這個方法簡便地實現了模式"如果有緩存則返回;否則運算、緩存、然后返回"。 ``` Cache<Key, Graph> cache = CacheBuilder.newBuilder() .maximumSize(1000) .build(); // look Ma, no CacheLoader ... try { // If the key wasn't in the "easy to compute" group, we need to // do things the hard way. cache.get(key, new Callable<Key, Graph>() { @Override public Value call() throws AnyException { return doThingsTheHardWay(key); } }); } catch (ExecutionException e) { throw new OtherException(e.getCause()); } ``` ### 顯式插入 使用[cache.put(key, value)](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/Cache.html#put%28K,%20V%29)方法可以直接向緩存中插入值,這會直接覆蓋掉給定鍵之前映射的值。使用Cache.asMap()視圖提供的任何方法也能修改緩存。但請注意,asMap視圖的任何方法都不能保證緩存項被原子地加載到緩存中。進一步說,asMap視圖的原子運算在Guava Cache的原子加載范疇之外,所以相比于Cache.asMap().putIfAbsent(K, V),Cache.get(K, Callable&lt;V&gt;) 應該總是優先使用。 ## 緩存回收 一個殘酷的現實是,我們幾乎一定沒有足夠的內存緩存所有數據。你你必須決定:什么時候某個緩存項就不值得保留了?Guava Cache提供了三種基本的緩存回收方式:基于容量回收、定時回收和基于引用回收。 ### 基于容量的回收(size-based eviction) 如果要規定緩存項的數目不超過固定值,只需使用[`CacheBuilder.maximumSize(long)`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/CacheBuilder.html#maximumSize%28long%29)。緩存將嘗試回收最近沒有使用或總體上很少使用的緩存項。——_警告_:在緩存項的數目達到限定值之前,緩存就可能進行回收操作——通常來說,這種情況發生在緩存項的數目逼近限定值時。 另外,不同的緩存項有不同的“權重”(weights)——例如,如果你的緩存值,占據完全不同的內存空間,你可以使用[`CacheBuilder.weigher(Weigher)`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/…ommon/cache/CacheBuilder.html#weigher%28com.google.common.cache.Weigher%29)指定一個權重函數,并且用[`CacheBuilder.maximumWeight(long)`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/CacheBuilder.html#maximumWeight%28long%29)指定最大總重。在權重限定場景中,除了要注意回收也是在重量逼近限定值時就進行了,還要知道重量是在緩存創建時計算的,因此要考慮重量計算的復雜度。 ``` LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() .maximumWeight(100000) .weigher(new Weigher<Key, Graph>() { public int weigh(Key k, Graph g) { return g.vertices().size(); } }) .build( new CacheLoader<Key, Graph>() { public Graph load(Key key) { // no checked exception return createExpensiveGraph(key); } }); ``` ### 定時回收(Timed Eviction) `CacheBuilder``提供兩種定時回收的方法:` * [`expireAfterAccess(long, TimeUnit)`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/…eBuilder.html#expireAfterAccess%28long,%20java.util.concurrent.TimeUnit%29):緩存項在給定時間內沒有被讀/寫訪問,則回收。請注意這種緩存的回收順序和基于大小回收一樣。 * [`expireAfterWrite(long, TimeUnit)`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/…heBuilder.html#expireAfterWrite%28long,%20java.util.concurrent.TimeUnit%29):緩存項在給定時間內沒有被寫訪問(創建或覆蓋),則回收。如果認為緩存數據總是在固定時候后變得陳舊不可用,這種回收方式是可取的。 如下文所討論,定時回收周期性地在寫操作中執行,偶爾在讀操作中執行。 #### 測試定時回收 對定時回收進行測試時,不一定非得花費兩秒鐘去測試兩秒的過期。你可以使用[`Ticker`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/base/Ticker.html)接口和[`CacheBuilder.ticker(Ticker)`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/…e/common/cache/CacheBuilder.html#ticker%28com.google.common.base.Ticker%29)方法在緩存中自定義一個時間源,而不是非得用系統時鐘。 ### 基于引用的回收(Reference-based Eviction) 通過使用弱引用的鍵、或弱引用的值、或軟引用的值,Guava Cache可以把緩存設置為允許垃圾回收: * [`CacheBuilder.weakKeys()`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheBuilder.html#weakKeys%28%29):使用弱引用存儲鍵。當鍵沒有其它(強或軟)引用時,緩存項可以被垃圾回收。因為垃圾回收僅依賴恒等式(==),使用弱引用鍵的緩存用==而不是equals比較鍵。 * [`CacheBuilder.weakValues()`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheBuilder.html#weakValues%28%29):使用弱引用存儲值。當值沒有其它(強或軟)引用時,緩存項可以被垃圾回收。因為垃圾回收僅依賴恒等式(==),使用弱引用值的緩存用==而不是equals比較值。 * [`CacheBuilder.softValues()`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheBuilder.html#softValues%28%29):使用軟引用存儲值。軟引用只有在響應內存需要時,才按照全局最近最少使用的順序回收。考慮到使用軟引用的性能影響,我們通常建議使用更有性能預測性的緩存大小限定(見上文,基于容量回收)。使用軟引用值的緩存同樣用==而不是equals比較值。 ### 顯式清除 任何時候,你都可以顯式地清除緩存項,而不是等到它被回收: * 個別清除:[`Cache.invalidate(key)`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/Cache.html#invalidate%28java.lang.Object%29) * 批量清除:[`Cache.invalidateAll(keys)`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/Cache.html#invalidateAll%28java.lang.Iterable%29) * 清除所有緩存項:[`Cache.invalidateAll()`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/Cache.html#invalidateAll%28%29) ### 移除監聽器 通過[`CacheBuilder.removalListener(RemovalListener)`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/…eBuilder.html#removalListener%28com.google.common.cache.RemovalListener%29),你可以聲明一個監聽器,以便緩存項被移除時做一些額外操作。緩存項被移除時,[`RemovalListener`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/RemovalListener.html)會獲取移除通知[`RemovalNotification`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/RemovalNotification.html),其中包含移除原因[`RemovalCause`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/RemovalCause.html)、鍵和值。 請注意,RemovalListener拋出的任何異常都會在記錄到日志后被丟棄[swallowed]。 ``` CacheLoader<Key, DatabaseConnection> loader = new CacheLoader<Key, DatabaseConnection> () { public DatabaseConnection load(Key key) throws Exception { return openConnection(key); } }; RemovalListener<Key, DatabaseConnection> removalListener = new RemovalListener<Key, DatabaseConnection>() { public void onRemoval(RemovalNotification<Key, DatabaseConnection> removal) { DatabaseConnection conn = removal.getValue(); conn.close(); // tear down properly } }; return CacheBuilder.newBuilder() .expireAfterWrite(2, TimeUnit.MINUTES) .removalListener(removalListener) .build(loader); ``` 警告:默認情況下,監聽器方法是在移除緩存時同步調用的。因為緩存的維護和請求響應通常是同時進行的,代價高昂的監聽器方法在同步模式下會拖慢正常的緩存請求。在這種情況下,你可以使用[`RemovalListeners.asynchronous(RemovalListener, Executor)`](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/…om.google.common.cache.RemovalListener,%20java.util.concurrent.Executor%29)把監聽器裝飾為異步操作。 ### 清理什么時候發生? 使用CacheBuilder構建的緩存不會"自動"執行清理和回收工作,也不會在某個緩存項過期后馬上清理,也沒有諸如此類的清理機制。相反,它會在寫操作時順帶做少量的維護工作,或者偶爾在讀操作時做——如果寫操作實在太少的話。 這樣做的原因在于:如果要自動地持續清理緩存,就必須有一個線程,這個線程會和用戶操作競爭共享鎖。此外,某些環境下線程創建可能受限制,這樣CacheBuilder就不可用了。 相反,我們把選擇權交到你手里。如果你的緩存是高吞吐的,那就無需擔心緩存的維護和清理等工作。如果你的 緩存只會偶爾有寫操作,而你又不想清理工作阻礙了讀操作,那么可以創建自己的維護線程,以固定的時間間隔調用[`Cache.cleanUp()`](http://docs.guava-libraries.googlecode.com/git-history/v11.0.1/javadoc/com/google/common/cache/Cache.html#cleanUp%28%29)。[`ScheduledExecutorService`](http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/ScheduledExecutorService.html)可以幫助你很好地實現這樣的定時調度。 ### 刷新 刷新和回收不太一樣。正如[LoadingCache.refresh(K)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/LoadingCache.html#refresh%28K%29)所聲明,刷新表示為鍵加載新值,這個過程可以是異步的。在刷新操作進行時,緩存仍然可以向其他線程返回舊值,而不像回收操作,讀緩存的線程必須等待新值加載完成。 如果刷新過程拋出異常,緩存將保留舊值,而異常會在記錄到日志后被丟棄[swallowed]。 重載[CacheLoader.reload(K, V)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/CacheLoader.html#reload%28K,%20V%29)可以擴展刷新時的行為,這個方法允許開發者在計算新值時使用舊的值。 ``` //有些鍵不需要刷新,并且我們希望刷新是異步完成的 LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() .maximumSize(1000) .refreshAfterWrite(1, TimeUnit.MINUTES) .build( new CacheLoader<Key, Graph>() { public Graph load(Key key) { // no checked exception return getGraphFromDatabase(key); } public ListenableFuture<Key, Graph> reload(final Key key, Graph prevGraph) { if (neverNeedsRefresh(key)) { return Futures.immediateFuture(prevGraph); }else{ // asynchronous! ListenableFutureTask<Key, Graph> task=ListenableFutureTask.create(new Callable<Key, Graph>() { public Graph call() { return getGraphFromDatabase(key); } }); executor.execute(task); return task; } } }); ``` [CacheBuilder.refreshAfterWrite(long, TimeUnit)](http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/…eBuilder.html#refreshAfterWrite%28long,%20java.util.concurrent.TimeUnit%29)可以為緩存增加自動定時刷新功能。和expireAfterWrite相反,refreshAfterWrite通過定時刷新可以讓緩存項保持可用,但請注意:緩存項只有在被檢索時才會真正刷新(如果CacheLoader.refresh實現為異步,那么檢索不會被刷新拖慢)。因此,如果你在緩存上同時聲明expireAfterWrite和refreshAfterWrite,緩存并不會因為刷新盲目地定時重置,如果緩存項沒有被檢索,那刷新就不會真的發生,緩存項在過期時間后也變得可以回收。 ## 其他特性 ### 統計 [`CacheBuilder.recordStats()`](http://docs.guava-libraries.googlecode.com/git-history/release12/javadoc/com/google/common/cache/CacheBuilder.html#recordStats%28%29)用來開啟Guava Cache的統計功能。統計打開后,[`Cache.stats()`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/Cache.html#stats%28%29)方法會返回[`CacheStats`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheStats.html)對象以提供如下統計信息: * [`hitRate()`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheStats.html#hitRate%28%29):緩存命中率; * [`averageLoadPenalty()`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheStats.html#averageLoadPenalty%28%29):加載新值的平均時間,單位為納秒; * [`evictionCount()`](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheStats.html#evictionCount%28%29):緩存項被回收的總數,不包括顯式清除。 此外,還有其他很多統計信息。這些統計信息對于調整緩存設置是至關重要的,在性能要求高的應用中我們建議密切關注這些數據。 ### asMap視圖 asMap視圖提供了緩存的ConcurrentMap形式,但asMap視圖與緩存的交互需要注意: * cache.asMap()包含當前所有加載到緩存的項。因此相應地,cache.asMap().keySet()包含當前所有已加載鍵; * asMap().get(key)實質上等同于cache.getIfPresent(key),而且不會引起緩存項的加載。這和Map的語義約定一致。 * 所有讀寫操作都會重置相關緩存項的訪問時間,包括Cache.asMap().get(Object)方法和Cache.asMap().put(K, V)方法,但不包括Cache.asMap().containsKey(Object)方法,也不包括在Cache.asMap()的集合視圖上的操作。比如,遍歷Cache.asMap().entrySet()不會重置緩存項的讀取時間。 ## 中斷 緩存加載方法(如Cache.get)不會拋出InterruptedException。我們也可以讓這些方法支持InterruptedException,但這種支持注定是不完備的,并且會增加所有使用者的成本,而只有少數使用者實際獲益。詳情請繼續閱讀。 Cache.get請求到未緩存的值時會遇到兩種情況:當前線程加載值;或等待另一個正在加載值的線程。這兩種情況下的中斷是不一樣的。等待另一個正在加載值的線程屬于較簡單的情況:使用可中斷的等待就實現了中斷支持;但當前線程加載值的情況就比較復雜了:因為加載值的CacheLoader是由用戶提供的,如果它是可中斷的,那我們也可以實現支持中斷,否則我們也無能為力。 如果用戶提供的CacheLoader是可中斷的,為什么不讓Cache.get也支持中斷?從某種意義上說,其實是支持的:如果CacheLoader拋出InterruptedException,Cache.get將立刻返回(就和其他異常情況一樣);此外,在加載緩存值的線程中,Cache.get捕捉到InterruptedException后將恢復中斷,而其他線程中InterruptedException則被包裝成了ExecutionException。 原則上,我們可以拆除包裝,把ExecutionException變為InterruptedException,但這會讓所有的LoadingCache使用者都要處理中斷異常,即使他們提供的CacheLoader不是可中斷的。如果你考慮到所有非加載線程的等待仍可以被中斷,這種做法也許是值得的。但許多緩存只在單線程中使用,它們的用戶仍然必須捕捉不可能拋出的InterruptedException異常。即使是那些跨線程共享緩存的用戶,也只是有時候能中斷他們的get調用,取決于那個線程先發出請求。 對于這個決定,我們的指導原則是讓緩存始終表現得好像是在當前線程加載值。這個原則讓使用緩存或每次都計算值可以簡單地相互切換。如果老代碼(加載值的代碼)是不可中斷的,那么新代碼(使用緩存加載值的代碼)多半也應該是不可中斷的。 如上所述,Guava Cache在某種意義上支持中斷。另一個意義上說,Guava Cache不支持中斷,這使得LoadingCache成了一個有漏洞的抽象:當加載過程被中斷了,就當作其他異常一樣處理,這在大多數情況下是可以的;但如果多個線程在等待加載同一個緩存項,即使加載線程被中斷了,它也不應該讓其他線程都失敗(捕獲到包裝在ExecutionException里的InterruptedException),正確的行為是讓剩余的某個線程重試加載。為此,我們記錄了一個[bug](https://code.google.com/p/guava-libraries/issues/detail?id=1122)。然而,與其冒著風險修復這個bug,我們可能會花更多的精力去實現另一個建議AsyncLoadingCache,這個實現會返回一個有正確中斷行為的Future對象。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看