Java的WeakHashMap通常被认为对缓存有用。尽管其弱引用是根据地图的键而不是其值定义的,但这似乎很奇怪。我的意思是,这是我要缓存的值,而一旦缓存中没有其他人强烈引用它们,我想获取垃圾回收值,不是吗?
以哪种方式保留对键的弱引用?如果执行a ExpensiveObject o = weakHashMap.get("some_key"),那么我希望高速缓存保持在'o'状态,直到调用者不再拥有强引用为止,并且我根本不在乎字符串对象“ some_key”。
我想念什么吗?
Java的WeakHashMap通常被认为对缓存有用。尽管其弱引用是根据地图的键而不是其值定义的,但这似乎很奇怪。我的意思是,这是我要缓存的值,而一旦缓存中没有其他人强烈引用它们,我想获取垃圾回收值,不是吗?
以哪种方式保留对键的弱引用?如果执行a ExpensiveObject o = weakHashMap.get("some_key"),那么我希望高速缓存保持在'o'状态,直到调用者不再拥有强引用为止,并且我根本不在乎字符串对象“ some_key”。
我想念什么吗?
Answers:
WeakHashMap不能用作缓存,至少在大多数人看来是这样。如您所说,它使用的是弱键而不是弱的值,因此它并不是针对大多数人想要使用的功能而设计的(事实上,我见过人们不正确地使用它)。
WeakHashMap最有用的是保留有关您无法控制其生命周期的对象的元数据。例如,如果您有一堆通过类的对象,并且您希望跟踪有关它们的额外数据,而无需在它们超出范围时得到通知,并且无需引用它们就可以使它们保持活动状态。
一个简单的示例(以及我之前使用过的示例)可能类似于:
WeakHashMap<Thread, SomeMetaData>
您可以在哪里跟踪系统中的各个线程在做什么;当线程死亡时,该条目将从您的映射中静默删除,并且如果您最后引用该线程,则不会阻止该线程被垃圾回收。然后,您可以遍历该映射中的条目,以查找有关系统中活动线程的元数据。
在非缓存中查看WeakHashMap!欲获得更多信息。
对于您想要的缓存类型,请使用专用的缓存系统(例如EHCache)或查看Guava的MapMaker类;就像是
new MapMaker().weakValues().makeMap();
将执行您想要的操作,或者如果您想花哨的话,可以添加定时到期时间:
new MapMaker().weakValues().expiration(5, TimeUnit.MINUTES).makeMap();
    主要用途WeakHashMap是当您具有要在其键消失时消失的映射。缓存是相反的-您具有要在其值消失时消失的映射。
对于缓存,您想要的是一个Map<K,SoftReference<V>>。SoftReference内存不足时,A将被垃圾回收。(将此与进行对比WeakReference,一旦不再有对其硬引用就可以清除它。)您希望您的引用在缓存中保持软化(至少在键值映射不陈旧的缓存中) ),从那时起,如果以后再查找它们,则您的值就有可能仍在缓存中。如果引用比较弱,那么您的值将立即被gc'击败,从而达到缓存的目的。
为了方便起见,您可能希望隐藏实现中的SoftReference值Map,以使缓存看起来是<K,V>而不是的类型<K,SoftReference<V>>。如果您想这样做,此问题对网络上的可用实现提出了建议。
还请注意,当您在中使用SoftReference值时Map,您必须执行一些操作以手动删除已SoftReferences清除了它们的键值对-否则,您的Map大小将永远增长,并泄漏内存。
Map<K, SoftRereference<?>>方法会SoftReference在包含null引用对象的映射中留下实例。我认为,此映射的内部实现必须定期清除所有映射,并使用一个值(该值是包含引用null对象的软引用)来进行良好的清理。
                    HashMap<K, SoftReference<V>>则将导致内存泄漏。您可能会考虑将其包含在您的答案中。看一下WeakHashMap它是如何进行的,Oracle JDK中有一个私有方法expungeStaleEntries来处理此清理工作。
                    要考虑的另一件事是,如果采用此Map<K, WeakReference<V>>方法,则值可能会消失,但映射不会消失。根据使用情况,您可能最终会得到一个包含许多条目的地图,这些条目的弱引用已被GC处理。
Map<K, V>不是Map<K, WeakReference<V>>。从表面上看,这个答案似乎是有道理的,但是请注意,每次用户调用时Map.get,都可以删除丢失的映射,并且看到这正是WeakHashMap删除键的方式,这不可能是Java团队并未意识到这一点。 。
                    您需要两个映射:一个映射在高速缓存键和弱引用值之间映射,一个映射在相反方向上在弱引用值和键之间映射。您需要一个参考队列和一个清理线程。
当被引用的对象无法再访问时,弱引用可以将其移入队列。此队列必须由清理线程清除。 为了进行清理,有必要获取密钥以供参考。 这就是为什么需要第二张地图的原因。
以下示例显示如何使用弱引用的哈希映射创建缓存。运行程序时,将得到以下输出:
$ javac -Xlint:未选中Cache.java && Java缓存
{偶数:[2,4,6],奇数:[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();
    }
}