具有弱值的 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,但那里的键很弱(并且值仍然是强引用),所以用 WeakReferences 做长是没有意义的。
解决此问题的最佳解决方案是什么?是否有一些“逆 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<WeakReference<CustomObject>,Long>
以知道如果在队列中找到引用,则要删除哪个条目。
或者,如果缓存不经常使用,您可以在单独的线程中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 [重复]