2009-11-26 53 views
55

Java的WeakHashMap經常被認爲對高速緩存很有用。看起來很奇怪,它的弱引用是根據地圖的鍵而定義的,而不是它的值。我的意思是,這是我想要緩存的值,並且我想要一次性收集垃圾,除了緩存之外沒有其他人強烈引用它們,不是嗎?Java的WeakHashMap和緩存:它爲什麼引用鍵而不是值?

它以哪種方式幫助弱鍵引用?如果你做了一個ExpensiveObject o = weakHashMap.get("some_key"),那麼我希望緩存保持'o',直到調用者不再擁有強引用,並且我完全不關心字符串對象「some_key」。

我錯過了什麼嗎?

+0

的Java API是充滿怪異的怪癖。您始終可以使用WeakReference重寫WeakHashMap。 – Pacerier 2017-09-18 05:08:44

回答

98

WeakHashMap 不是作爲緩存很有用,至少大多數人認爲它是有用的。正如你所說,它使用弱,而不是弱,所以它不是爲大多數人想用它的設計(事實上,我已經看到人們使用它,不正確)。

WeakHashMap主要用於保存有關其生命週期不受控制的對象的元數據。例如,如果您有一堆對象在您的課程中傳遞,並且您希望跟蹤有關它們的額外數據,而不需要在超出範圍時收到通知,並且不需要引用它們就可以保持活動狀態。

一個簡單的例子(和一個我以前用過的)可能是這樣的:

WeakHashMap<Thread, SomeMetaData> 

,你可能會跟蹤哪些不同的線程在您的系統正在做的;當線程死亡時,該條目將從您的映射中靜默移除,並且如果您是最後一個引用,則不會阻止線程被垃圾收集。然後,您可以遍歷該映射中的條目,以找出有關係統中活動線程的元數據。

請參閱WeakHashMap in not a cache!瞭解更多信息。

對於您所使用的緩存類型,請使用專用緩存系統(例如EHCache)或查看google-collections'MapMaker class;像

new MapMaker().weakValues().makeMap(); 

會做你以後,或者如果你想獲得看上你可以添加定時到期:

new MapMaker().weakValues().expiration(5, TimeUnit.MINUTES).makeMap(); 
+4

爲了更新2013年8月的版本:Google Collections現在名爲Guava,緩存創建邏輯現在是[CacheBuilder]的一部分(http://docs.guava-libraries.googlecode.com/git-history/release/ javadoc/index.html)類。 – 2013-08-04 03:33:43

+0

更精確鏈接:http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/CacheBuilder.html – 2013-09-25 14:54:55

+1

注意,我覺得在你的例子對地圖製作工具你意思是說新的MapMaker()。softValues()。makeMap(),因爲調用weakValues()可以得到與WeakHashMap相同的結果。有一個很好的例子,這裏如何建立與地圖製作工具的高速緩存 - http://stackoverflow.com/questions/3737140/use-of-google-collections-mapmaker – jklp 2013-11-13 04:23:02

30

WeakHashMap的主要用途是,當你擁有了你想要的映射當他們的鑰匙消失時消失。緩存是相反的 - 你有映射,當它們的值消失時你想消失。

對於緩存,你想要的是一個Map<K,SoftReference<V>>。當內存變得緊張時,SoftReference將被垃圾收集。 (將其與WeakReference進行對比,只要不再對其指示對象進行硬引用,就可以將其清除)。您希望引用在緩存中很軟(至少在鍵值映射不去的地方)陳舊),因爲如果您稍後再查找它們,您的值仍有可能仍在緩存中。如果引用很弱,那麼您的值將立即被刷新,從而破壞緩存的目的。

爲方便起見,您可能想要在Map實現中隱藏SoftReference值,以便緩存看起來類型爲<K,V>而不是<K,SoftReference<V>>。如果你想這樣做,this question有關於網絡上可用實現的建議。

還要注意的是,當你在一個Map使用SoftReference值,你必須東西,以去除有他們SoftReferences清除鍵 - 值對---否則你的Map將導致內存泄漏。

+0

隨着時間的推移使用這個解決方案給你留下了許多散列表項,這個值已經被gc-ed了。有沒有其他方法使用類似的方法? – 2013-01-27 23:37:19

+0

一個'Map >'方法在GC運行後在包含'null'對象的映射中留下'SoftReference'的實例。我認爲這個映射的內部實現必須週期性地清除所有映射的值,該值是一個持有'空'參照的軟引用,以便進行良好的清理。 – Timmos 2015-08-19 13:42:59

+2

(續)嚴格來說,如果一個天真的程序員使用實現'HashMap >',那麼這將導致內存泄漏。你可能會考慮在你的答案中包含這個。看一下'WeakHashMap'是如何實現的,Oracle JDK有一個私有的方法'expungeStaleEntries',它負責這個清理。 – Timmos 2015-08-19 13:50:35

6

另一個要考慮的是,如果你把Map<K, WeakReference<V>>方法,價值可能會消失,但映射不會。根據使用情況,您最終可能會得到一個包含許多弱引用已被GC化的條目的地圖。

+0

'Map ',而不是'Map >''。這個答案似乎在表面上是有意義的,但是請注意,每次用戶調用'Map.get'時,都會刪除缺少的映射,並且看到[這正是WeakHashMap刪除鍵的方式](http://archive.is/ Z3aK9#selection-1069.117-1069.237),它不可能是Java團隊沒有意識到這一點。 – Pacerier 2017-09-18 05:31:39

6

需要兩個映射:一個用來在弱引用的值和鍵之間的相對方向映射緩存鍵和weak referenced值和一之間映射。你需要一個reference queue和一個清理線程。

弱引用必須參考移動到隊列時被引用的對象不能訪問任何更長的能力。該隊列必須由清理線程清除。 而對於清理,有必要獲得參考的關鍵。這就是爲什麼第二張地圖是必需的原因。

下面的示例演示如何創建具有弱引用的哈希映射中的緩存。當你運行該程序將得到以下的輸出:

 
$ javac -Xlint:unchecked Cache.java && java Cache 
{even: [2, 4, 6], odd: [1, 3, 5]} 
{even: [2, 4, 6]} 

,第一行顯示的緩存中的內容之前,參考奇數名單已經被刪除其勝算之後的第二行已被刪除。

這是代碼:

import java.lang.ref.Reference; 
import java.lang.ref.ReferenceQueue; 
import java.lang.ref.WeakReference; 
import java.util.Arrays; 
import java.util.Collections; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 

class Cache<K,V> 
{ 
    ReferenceQueue<V> queue = null; 
    Map<K,WeakReference<V>> values = null; 
    Map<WeakReference<V>,K> keys = null; 
    Thread cleanup = null; 

    Cache() 
    { 
     queue = new ReferenceQueue<V>(); 
     keys = Collections.synchronizedMap (new HashMap<WeakReference<V>,K>()); 
     values = Collections.synchronizedMap (new HashMap<K,WeakReference<V>>()); 
     cleanup = new Thread() { 
       public void run() { 
        try { 
         for (;;) { 
          @SuppressWarnings("unchecked") 
          WeakReference<V> ref = (WeakReference<V>)queue.remove(); 
          K key = keys.get(ref); 
          keys.remove(ref); 
          values.remove(key); 
         } 
        } 
        catch (InterruptedException e) {} 
       } 
      }; 
     cleanup.setDaemon (true); 
     cleanup.start(); 
    } 

    void stop() { 
     cleanup.interrupt(); 
    } 

    V get (K key) { 
     return values.get(key).get(); 
    } 

    void put (K key, V value) { 
     WeakReference<V> ref = new WeakReference<V>(value, queue); 
     keys.put (ref, key); 
     values.put (key, ref); 
    } 

    public String toString() { 
     StringBuilder str = new StringBuilder(); 
     str.append ("{"); 
     boolean first = true; 
     for (Map.Entry<K,WeakReference<V>> entry : values.entrySet()) { 
      if (first) 
       first = false; 
      else 
       str.append (", "); 
      str.append (entry.getKey()); 
      str.append (": "); 
      str.append (entry.getValue().get()); 
     } 
     str.append ("}"); 
     return str.toString(); 
    } 

    static void gc (int loop, int delay) throws Exception 
    { 
     for (int n = loop; n > 0; n--) { 
      Thread.sleep(delay); 
      System.gc(); // <- obstinate donkey 
     } 
    } 

    public static void main (String[] args) throws Exception 
    { 
     // Create the cache 
     Cache<String,List> c = new Cache<String,List>(); 

     // Create some values 
     List odd = Arrays.asList(new Object[]{1,3,5}); 
     List even = Arrays.asList(new Object[]{2,4,6}); 

     // Save them in the cache 
     c.put ("odd", odd); 
     c.put ("even", even); 

     // Display the cache contents 
     System.out.println (c); 

     // Erase one value; 
     odd = null; 

     // Force garbage collection 
     gc (10, 10); 

     // Display the cache again 
     System.out.println (c); 

     // Stop cleanup thread 
     c.stop(); 
    } 
} 
+1

很棒的回答。值得注意的是,ReferenceQueue與許多其他種類的集合不同,阻塞直到可以從queue.remove()返回一個值。這意味着清理線程不是第一眼可能提示的非等待無限循環。 – 2015-04-27 07:55:40

+0

@Gordon如果你使用弱鍵和弱值,一切都很微弱,並且會在你將它添加到緩存後收集垃圾。 – ceving 2015-07-16 12:34:11

+0

**這是答案**。所以我們可以這麼說,因此它是爲了**優化清理階段**而實現的API。 – Pacerier 2017-09-18 05:42:50

相關問題