65

我知道有一个WeakHashMapin java.util,但是因为它使用WeakReferences 来表示所有东西,它只被 this引用,所以被Map引用的对象将在下一个 GC 循环中丢失。因此,如果您想缓存随机数据,这几乎是没有用的,很可能会再次请求而不会在其余时间进行硬链接。最好的解决方案是使用SoftReferences 代替的地图,但我在 Java RT 包中没有找到。

4

7 回答 7

31

编辑(2012 年 8 月):

事实证明,目前最好的解决方案可能是 Guava 13.0 的Cache类,在Guava 的 Wiki上进行了解释——这就是我要使用的。它甚至支持构建一个SoftHashMap(请参阅 参考资料CacheBuilder.newBuilder().softKeys()),但它可能不是您想要的,正如 Java 专家 Jeremy Manson 解释的那样(您将在下面找到链接)。


不是我所知道的SoftHashMap(2008 年 11 月),但是您可以在网上找到一些实现。

喜欢这个:SoftHashMap或者这个


编辑(2009 年 11 月)
正如Matthias在评论中提到的,Google Guava MapMaker确实使用了 SoftReferences:

一个ConcurrentMap构建器,提供以下功能的任意组合:

  • 软键或弱键,
  • 软或弱的价值观,
  • 定时到期,和
  • 按需计算值。

正如这个线程中提到的,另一个 JSR166y 候选者:

jsr166y.ConcurrentReferenceHashMap

它为 Google 实现提供了一个替代的并发引用映射(它依赖于后台线程来驱逐条目)


编辑(2012 年 8 月)

仅当请求条目的定时到期时,Google 实现才使用后台线程。特别是,它只是简单地使用java.util.Timer,它不像拥有一个单独的后台线程那样具有侵入性。

Jeremy Manson 建议,对于任何缓存,使用此功能以避免 SoftReference 的危险:http: //jeremymanson.blogspot.de/2009/07/how-hotspot-decides-to-clear_07.html

Apache Commons还有另一个实现,即org.apache.commons.collections.map.ReferenceMap;它不支持定时删除,但它支持选择是否应该按身份或相等来比较密钥。此外,这个实现不是并发的——它可以同步,但在来自多个线程的访问下效果不太好。

于 2008-11-05T08:11:23.163 回答
21

我熟悉两个提供 SoftHashMap 实现的库:

  1. Apache Commons:org.apache.commons.collections.map.ReferenceMap

  2. 谷歌收藏:com.google.common.collect.ReferenceMap

于 2008-12-30T03:56:02.053 回答
4

98期java专家时事通讯中有一个示例实现

于 2008-11-05T08:45:21.537 回答
2

您是否考虑过使用LRUMap而不是软 HashMap?您可以更好地控制存储的内容(或至少存储多少)。

于 2009-11-26T16:37:02.407 回答
2

Apache Shiro带有一个为缓存而设计的 SoftHashMap。它基于上面 jb 发布的文章并在 Apache v2 下获得许可。您可以在此处找到文档,并在此处找到源代码。

于 2011-10-05T15:27:15.090 回答
1

如果你想实现一个缓存软引用绝对是一个比弱引用更好的主意,但是它将你的整个缓存删除策略交给了垃圾收集器。这可能不是你想要的。

如果缓存删除策略很重要,您很可能需要使用常规引用自行完成。但是,您将不得不决定何时弹出项目以及弹出哪些项目。如果您只想在堆空间用完时丢失东西,您可以通过以下方式查询可用堆空间:

Runtime.getRuntime().getFreeMemory();

然后,一旦可用内存下降到一定数量以下,您就可以开始丢弃项目。或者你可以为缓存实现一个最大大小,并使用它来决定什么时候丢弃东西。

这是我设计的具有 O(1) 插入、删除和查找时间的LRU 缓存,它具有可配置的最大元素数。如果你想要一个缓存,这将是一个比 SoftHashMap 更好的解决方案。

软引用是创建可增长缓存的好方法。因此,理想的解决方案是使用 SoftHashMap 以及常规的固定大小缓存。让所有插入到缓存中的内容都进入固定缓存和软哈希映射,然后引用一些东西,看看它是否在软哈希映射中(并更新缓存中的参考时间)。这样,您所有最重要的项目(根据您选择的策略 LRU、MFU、...)将永远不会被删除,因为它们在缓存中被硬引用,但您也将保留更多的东西(没有策略控制)只要因为有足够的内存。

于 2008-11-05T15:54:24.023 回答
1

例如,您可以使用此实现线程安全的软参考 HashMap:

package cz.b2b.jcl.util;

import java.util.*;
import java.lang.ref.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.AbstractMap.SimpleImmutableEntry;

public class ConcurrentSoftHashMap<K, V> extends AbstractMap {

    /**
     The internal HashMap that will hold the SoftReference.
     */
    private final Map<Object, SoftReference> hash = new ConcurrentHashMap<>();
    /**
     The number of "hard" references to hold internally.
     */
    private final int HARD_SIZE;
    /**
     The FIFO list of hard references, order of last access.
     */
    private final ConcurrentLinkedSetQueue hardCache = new ConcurrentLinkedSetQueue();
    /**
     Reference queue for cleared SoftReference objects.
     */
    private final ReferenceQueue queue = new ReferenceQueue();

    public ConcurrentSoftHashMap(int hardSize) {
        HARD_SIZE = hardSize;
    }

    @Override
    public Object get(Object key) {
        Object result = null;
        // We get the SoftReference represented by that key
        SoftReference soft_ref = hash.get(key);
        if (soft_ref != null) {
            // From the SoftReference we get the value, which can be
            // null if it was not in the map, or it was removed in
            // the processQueue() method defined below
            result = soft_ref.get();
            if (result == null) {
                // If the value has been garbage collected, remove the
                // entry from the HashMap.
                hash.remove(key);
            } else {
                // We now add this object to the beginning of the hard
                // reference queue.  
                hardCache.enqueue(result);
                if (hardCache.size() > HARD_SIZE) {
                    // Remove the last entry if list longer than HARD_SIZE
                    hardCache.dequeue();
                }
            }
        }
        return result;
    }

    /**
     Here we put the key, value pair into the HashMap using
     a SoftValue object.
     @param key
     @param value
     @return
     */
    @Override
    public Object put(Object key, Object value) {
        processQueue(); // throw out garbage collected values first
        return hash.put(key, new SoftValue(value, key, queue));
    }

    @Override
    public Object remove(Object key) {
        processQueue(); // throw out garbage collected values first
        return hash.remove(key);
    }

    @Override
    public void clear() {
        hardCache.clear();
        processQueue(); // throw out garbage collected values
        hash.clear();
    }

    @Override
    public int size() {
        processQueue(); // throw out garbage collected values first
        return hash.size();
    }

    @Override
    public boolean containsKey(Object key) {
        processQueue(); // throw out garbage collected values first
        return hash.containsKey(key);
    }

    @Override
    public Set entrySet() {
        Set<Map.Entry> entry = new HashSet<>();
        Map.Entry simpleImmutableEntry = null;
        Object result = null;
        processQueue(); // throw out garbage collected values first
        for (Map.Entry<Object, SoftReference> item : hash.entrySet()) {
            if (item == null) {
                continue;
            }
            Object key = item.getKey();
            SoftReference soft_ref = item.getValue();
            if (soft_ref != null) {
                result = soft_ref.get();
                if (result == null) {
                    hash.remove(key);
                } else {
                    hardCache.enqueue(result);
                    if (hardCache.size() > HARD_SIZE) {
                        hardCache.dequeue();
                    }
                    simpleImmutableEntry = new SimpleImmutableEntry(key, result);
                    entry.add(simpleImmutableEntry);

                }
            }

        }

        return entry;
    }

    private class ConcurrentLinkedSetQueue<E> extends ConcurrentLinkedQueue<E> {

        public void enqueue(E o) {
            if (!contains(o)) {
                add(o);
            }
        }

        public E dequeue() {
            return poll();
        }

    }

    /**
     We define our own subclass of SoftReference which contains
     not only the value but also the key to make it easier to find
     the entry in the HashMap after it's been garbage collected.
     */
    private static class SoftValue extends SoftReference {

        private final Object key; // always make data member final

        /**
         Did you know that an outer class can access private data
         members and methods of an inner class? I didn't know that!
         I thought it was only the inner class who could access the
         outer class's private information. An outer class can also
         access private members of an inner class inside its inner
         class.
         */
        private SoftValue(Object k, Object key, ReferenceQueue q) {
            super(k, q);
            this.key = key;
        }
    }

    /**
     Here we go through the ReferenceQueue and remove garbage
     collected SoftValue objects from the HashMap by looking them
     up using the SoftValue.key data member.
     */
    private void processQueue() {
        SoftValue sv;
        while ((sv = (SoftValue) queue.poll()) != null) {
            hash.remove(sv.key); // we can access private data!
        }
    }

}
于 2020-07-10T13:07:22.030 回答