具有弱值的 HashMap

Posted

技术标签:

【中文标题】具有弱值的 HashMap【英文标题】:HashMap with weak values 【发布时间】:2012-11-04 23:34:55 【问题描述】:

我正在为持久存储的对象实现缓存。这个想法是:

方法getObjectFromPersistence(long id); ///Takes about 3 seconds 方法getObjectFromCache(long id) //Instantly

还有一个方法:getObject(long id),伪代码如下:

synchronized(this)
    CustomObject result= getObjectFromCache(id)
    if (result==null)
       result=getObjectFromPersistence(id);
       addToCache(result);
    
    return result;

但我需要允许垃圾收集器收集 CustomObject。到目前为止,我一直在使用 HashMap<Long,WeakReference<CustomObject> 来实现。问题是随着时间的推移,HashMap 会被空的WeakReferences 填充。

我检查了WeakHashMap,但那里的键很弱(并且值仍然是强引用),所以用 Wea​​kReferences 做长是没有意义的。

解决此问题的最佳解决方案是什么?是否有一些“逆 WeakHashMap”或类似的东西?

谢谢

【问题讨论】:

您是否查看过 Google Guava 库中的缓存功能?我认为它可能包含您正在寻找的一些功能。 code.google.com/p/guava-libraries/wiki/… 【参考方案1】:

Apache Commons Collections中有ReferenceMap,这是一个有硬键和软值的映射实现(与WeakHashMap相反)。

【讨论】:

【参考方案2】:

我需要存储带标签的弱对象,我想不用WeakHashMap<String, T>,我可以只用WeakHashMap<T, String>

这是 Kotlin,但应该同样适用于 Java:

abstract class InstanceFactory<T> 
    @Volatile
    private var instances: MutableMap<T, String> = WeakHashMap<T, String>()

    protected fun getOrCreate(tag: String = SINGLETON, creator: () -> T): T =
        findByTag(tag)?.let 
            it
         ?: synchronized(this) 
            findByTag(tag)?.let 
                it
             ?: run 
                creator().also 
                    instances[it] = tag
                
            
        

    private fun findByTag(tag: String): T? = instances.entries.find  it.value == tag ?.key

    companion object 
        const val SINGLETON = "singleton"
    

可以这样使用:

class Thing(private val dependency: Dep)  ... 

class ThingFactory(private val dependency: Dep) : InstanceFactory<Thing>() 

    createInstance(tag: String): Thing = getOrCreate(tag)  Thing(dependency) 


简单的单例可以这样完成:

object ThingFactory 
    getInstance(dependency: Dependency): Thing = getOrCreate  Thing(dependency) 

【讨论】:

【参考方案3】:

您可以为此使用Guava MapMaker

ConcurrentMap<Long, CustomObject> graphs = new MapMaker()
   .weakValues()
   .makeMap();

您甚至可以通过将makeMap() 替换为以下内容来包含计算部分:

   .makeComputingMap(
       new Function<Long, CustomObject>() 
         public CustomObject apply(Long id) 
           return getObjectFromPersistence(id);
         
       );

由于您正在编写的内容看起来很像缓存,因此更新、更专业的Cache(通过CacheBuilder 构建)可能与您更相关。它不直接实现Map 接口,但提供了更多您可能需要的缓存控件。

您可以参考this 了解如何使用 CacheBuilder 的详细信息,这里是一个快速访问的示例:

LoadingCache<Integer, String> cache = CacheBuilder.newBuilder()
   .maximumSize(100)
   .expireAfterWrite(10, TimeUnit.MINUTES)
   .build(
       new CacheLoader<Integer, String>() 
           @Override
           public String load(Integer id) throws Exception 
               return "value";
           
       
   ); 

【讨论】:

【参考方案4】:

我认为最好的选择(如果不希望依赖 Guava)是使用一个自定义的 WeakReference 子类来记住其 ID,以便您的清理线程可以在清理 WeakReferences 期间删除弱值。

弱引用的实现,以及必要的 ReferenceQueue 和清理线程看起来像这样:

class CustomObjectAccess 

    private static final ReferenceQueue<CustomObject> releasedCustomObjects = 
                                                                  new ReferenceQueue<>();

    static 
        Thread cleanupThread = new Thread("CustomObject cleanup thread")                  
            while (true) 
                CustomObjectWeakReference freed = (CustomObjectWeakReference) 
                                CustomObjectWeakReference.releasedCustomObjects.remove();
                cache.remove(freed.id);
            
        ;
        cleanupThread.start();
    

    private Map<CustomObjectID, CustomObjectWeakReference> cache;

    public CustomObject get(CustomObjectID id) 
        synchronized(this)
            CustomObject result= getFromCache(id);
            if (result==null) 
                result=getObjectFromPersistence(id);
                addToCache(result);
            
        
        return result;
    

    private addToCache(CustomObject co) 
        cache.put(CustomObject.getID(), new CustomObjectWeakReference(co));
    

    private getFromCache(CustomObjectID id) 
        WeakReference<CustomObject> weak = cache.get(id);
        if (weak != null) 
            return weak.get();
        
        return null;
    

    class CustomObjectWeakReference extends WeakReference<CustomObject> 

        private final CustomObjectID id;

        CustomObjectWeakReference(CustomObject co) 
            super(co, releasedCustomObjects);
            this.id = co.getID();
        
    

【讨论】:

【参考方案5】:

WeakReference 被添加到它的 ReferenceQueue 中,并在收集其引用时在构造时提供。

您可以在访问缓存时使用poll ReferenceQueue,并持有HashMap&lt;WeakReference&lt;CustomObject&gt;,Long&gt; 以知道如果在队列中找到引用,则要删除哪个条目。

或者,如果缓存不经常使用,您可以在单独的线程中watch the queue。

【讨论】:

【参考方案6】:

您也可以从 jboss-common http://docs.jboss.org/jbossas/javadoc/4.0.2/org/jboss/util/collection/WeakValueHashMap.java.html 测试 WeakValueHashMap

【讨论】:

【参考方案7】:

您是否尝试过android.util.LruCache(它是一个SDK11 类,但它也在兼容包中,如android.support.v4.util.LruCache)。它没有实现java.util.Map,但工作起来就像一个地图,你可以定义它需要多少内存,它会刷新旧的(未使用的缓存对象本身)。

【讨论】:

【参考方案8】:

您可以开始“清理” - 每隔一段时间进行一次线程。也许如果您的地图大小超过阈值,但最多每 5 分钟一次......类似的事情。

保持清理周期较短,以免阻塞主要功能。

【讨论】:

或者您可以在每个请求上清除 x 个条目。更容易实施并且不会停止世界。 优秀,一月。这应该以低复杂性完成工作。 好主意,@JanDvorak。有一个缺点:不经常访问的缓存可能仍会保留未使用数据的时间比预期的要长。只要经常使用缓存,这就是一个非常好的策略。

以上是关于具有弱值的 HashMap的主要内容,如果未能解决你的问题,请参考以下文章

将 HashMap 转换为具有相同排序的 ArrayList [重复]

ArrayMap和HashMap区别

将具有多列值的表连接到具有多行值的表

java集合-HashMap

一个具有可变表/字段/值的函数,还是具有可变值的 60 个函数更好?

在 JPA 中,我如何找到具有属性值的所有类型和具有属性值的 ManyTomany 相关实体?