Java中是否有SoftHashMap?

71

我知道在java.util中有一个WeakHashMap,但是它使用WeakReference来引用每个对象,而这些对象只被Map所引用。因此,在下一次垃圾回收期间,被引用的对象将会丢失。所以如果您想缓存随机数据,并且很可能会再次请求该数据,但在其余时间内没有被硬链接,这个方法几乎是无用的。最好的解决方案是使用SoftReference的映射,但在Java RT包中我没有找到这样的映射。

7个回答

32

编辑(2012年8月):

事实证明,目前最好的解决方案可能是 Guava 13.0 的 Cache 类,可以在 Guava's Wiki 上找到解释 - 这就是我将要使用的内容。它甚至支持构建一个 SoftHashMap(参见 CacheBuilder.newBuilder().softKeys()),但这可能不是您想要的,正如Java专家Jeremy Manson所解释的那样(下面您会找到链接)。


据我所知(截至2008年11月),您可以在网络上找到一些实现了SoftHashMap的程序。

例如 SoftHashMap 或者 这个


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

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

  • 软键或弱键
  • 软值或弱值
  • 定时过期
  • 按需计算值

正如this thread所述,另一个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;它不支持定时删除,但它支持选择是通过标识还是相等性比较键。此外,此实现不是并发的-它可以被同步,但在多个线程访问下效果不佳。


另一个有用的实现是OpenJDK自己的sun.security.util.Cache,它支持最大大小、有限生命周期和普通引用与SoftReferences的选择。Java 7中的版本将对象映射到对象;Java 8中的版本是通用的。显然,我们不应该直接导入sun.的东西,但这段代码是GPL的,如果你想要一个起点,可以复制出来。 - Ti Strga

21

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

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

  2. Google Collections:com.google.common.collect.ReferenceMap


3
Google Collections已经放弃了ReferenceMap。现在请使用MapMaker。 - mxk

4

1
我在一个项目中使用了Kabutz博士的98期SoftHashMap版本已经将近六年了,对于这个特定的用途它表现得非常好。这里似乎有点长,但你可以在jidesoft.swing.SoftHashMap和org.jvnet.substance.utils.SoftHashMap中找到一个例子。在org.pushingpixels.substance.internal.utils.SoftHashMap中有一个调整过的版本。 - jla

2

您是否考虑使用LRUMap而不是软HashMap?这样您可以更好地控制存储的内容(或者至少能够控制存储的数量)。


1
LRU是一种有效的老派方法。但是,软引用缓存应该能够更好地适应缓存和内存使用中的峰值和谷值。可能这就是为什么OP首先要求一个WeakReferenceMap的SoftReference模拟。 - Javier

2
Apache Shiro附带了一个用于缓存的SoftHashMap。它基于jb发布的文章,并在Apache v2下获得许可。您可以在此处找到文档here和源代码here

1

如果您想要实现缓存,软引用绝对比弱引用更好的选择,但这会将整个缓存删除策略交给垃圾回收器,这可能不是您想要的。

如果缓存删除策略很重要,您很可能需要使用常规引用自己来完成。然而,您需要决定何时弹出项目以及哪些项目应该弹出。如果您只想在堆空间不足时丢失东西,可以通过查询可用堆空间来实现:

Runtime.getRuntime().getFreeMemory();

当可用内存降至一定程度时,您可以开始删除项目。或者,您可以实现缓存的最大大小,并使用它来决定何时删除内容。

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

软引用是创建可增长缓存的好方法。因此,理想的解决方案是同时使用SoftHashMap和常规固定大小缓存。将所有插入缓存的内容都放入固定缓存和软哈希映射中,然后只需查看软哈希映射中是否存在对某个内容的引用(并在缓存中更新引用时间),即可引用该内容。这样,根据您选择的策略(LRU、MFU等),所有最重要的项目都不会被删除,因为它们在缓存中被硬引用,但只要有足够的内存,您也将保留更多的内容(没有策略控制)。


1
正如Jeremy Manson所解释的那样,定时删除(从MapMaker中)是解决SoftReferences问题的好方法: http://jeremymanson.blogspot.de/2009/07/how-hotspot-decides-to-clear_07.html 否则,Apache Commons维护了一个LRUMap的实现,由Joel在下面提到。 - Blaisorblade

1
你可以使用线程安全的软引用哈希映射实现,例如:

SoftReferenceHashMap


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!
        }
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接