Java 的 WeakHashMap 和缓存:为什么引用键而不是值?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1802809/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java's WeakHashMap and caching: Why is it referencing the keys, not the values?
提问by Matthias
Java's WeakHashMapis often cited as being useful for caching. It seems odd though that its weak references are defined in terms of the map's keys, not its values. I mean, it's the values I want to cache, and which I want to get garbage collected once no-one else besides the cache is strongly referencing them, no?
Java 的WeakHashMap经常被认为对缓存很有用。虽然它的弱引用是根据地图的键而不是它的值定义的,但这似乎很奇怪。我的意思是,这是我想要缓存的值,并且一旦除了缓存之外没有其他人强烈引用它们,我想要垃圾收集,不是吗?
In which way does it help to hold weak references to the keys? If you do a ExpensiveObject o = weakHashMap.get("some_key")
, then I want the cache to hold on to 'o' until the caller doesn't hold the strong reference anymore, and I don't care at all about the string object "some_key".
保持对键的弱引用有什么帮助?如果您执行 a ExpensiveObject o = weakHashMap.get("some_key")
,那么我希望缓存保留 'o' 直到调用者不再持有强引用,而且我根本不在乎字符串对象“some_key”。
Am I missing something?
我错过了什么吗?
采纳答案by Cowan
WeakHashMap isn'tuseful as a cache, at least the way most people think of it. As you say, it uses weak keys, not weak values, so it's not designed for what most people want to use it for (and, in fact, I've seenpeople use it for, incorrectly).
WeakHashMap不能用作缓存,至少在大多数人的看法中是这样。正如你所说,它使用弱键,而不是弱值,所以它不是为大多数人想要使用它而设计的(事实上,我看到人们错误地使用它)。
WeakHashMap is mostly useful to keep metadata about objects whose lifecycle you don't control. For example, if you have a bunch of objects passing through your class, and you want to keep track of extra data about them without needing to be notified when they go out of scope, and without your reference to them keeping them alive.
WeakHashMap 主要用于保留有关其生命周期不受您控制的对象的元数据。例如,如果您有一堆对象通过您的类,并且您想跟踪有关它们的额外数据,而无需在它们超出范围时得到通知,并且无需对它们的引用来保持它们的活动。
A simple example (and one I've used before) might be something like:
一个简单的例子(也是我以前用过的)可能是这样的:
WeakHashMap<Thread, SomeMetaData>
where you might keep track of what various threads in your system are doing; when the thread dies, the entry will be removed silently from your map, and you won't keep the Thread from being garbage collected if you're the last reference to it. You can then iterate over the entries in that map to find out what metadata you have about active threads in your system.
您可以在哪里跟踪系统中的各种线程正在做什么;当线程终止时,条目将从您的地图中无声地删除,并且如果您是对它的最后一个引用,则不会阻止线程被垃圾收集。然后,您可以遍历该映射中的条目,以找出有关系统中活动线程的元数据。
See WeakHashMap in not a cache!for more information.
在不是缓存中查看WeakHashMap!想要查询更多的信息。
For the type of cache you're after, either use a dedicated cache system (e.g. EHCache) or look at Guava's MapMaker class; something like
对于您所追求的缓存类型,要么使用专用缓存系统(例如EHCache),要么查看Guava的MapMaker 类;就像是
new MapMaker().weakValues().makeMap();
will do what you're after, or if you want to get fancy you can add timed expiration:
会做你想做的事,或者如果你想花哨,你可以添加定时过期:
new MapMaker().weakValues().expiration(5, TimeUnit.MINUTES).makeMap();
回答by uckelman
The main use for WeakHashMap
is when you have mappings which you want to disappear when their keys disappear. A cache is the reverse---you have mappings which you want to disappear when their values disappear.
主要用途WeakHashMap
是当您有要在其键消失时消失的映射时。缓存是相反的——当它们的值消失时,你有想要消失的映射。
For a cache, what you want is a Map<K,SoftReference<V>>
. A SoftReference
will be garbage-collected when memory gets tight. (Contrast this with a WeakReference
, which may be cleared as soon as there is no longer a hard reference to its referent.) You want your references to be soft in a cache (at least in one where key-value mappings don't go stale), since then there is a chance that your values will still be in the cache if you look for them later. If the references were weak instead, your values would be gc'd right away, defeating the purpose of caching.
对于缓存,您想要的是Map<K,SoftReference<V>>
. SoftReference
当内存紧张时,A将被垃圾收集。(将此与 aWeakReference
形成对比,一旦不再有对其所指对象的硬引用,它就可能被清除。)您希望您的引用在缓存中是软的(至少在键值映射不会过时的情况下) ),从那时起,如果您稍后查找它们,您的值可能仍会在缓存中。如果引用较弱,则您的值将立即被垃圾回收,从而达不到缓存的目的。
For convenience, you might want to hide the SoftReference
values inside your Map
implementation, so that your cache appears to be of type <K,V>
instead of <K,SoftReference<V>>
. If you want to do that, this questionhas suggestions for implementations available on the net.
为方便起见,您可能希望SoftReference
在您的Map
实现中隐藏这些值,以便您的缓存看起来是类型<K,V>
而不是<K,SoftReference<V>>
. 如果你想这样做,这个问题有关于网络上可用实现的建议。
Note also that when you use SoftReference
values in a Map
, you mustdo something to manually remove key-value pairs which have had their SoftReferences
cleared---otherwise your Map
will only grow in size forever, and leak memory.
另请注意,当您SoftReference
在 a 中使用值时Map
,您必须手动删除已清除的键值对SoftReferences
——否则您的Map
大小只会永远增长,并泄漏内存。
回答by Shannon
Another thing to consider is that if you take the Map<K, WeakReference<V>>
approach, the value may disappear, but the mapping will not. Depending on usage, you may as a result end up with a Map containing many entries whose Weak References have been GC'd.
要考虑的另一件事是,如果您采用这种Map<K, WeakReference<V>>
方法,值可能会消失,但映射不会。根据使用情况,您最终可能会得到一个 Map,其中包含许多弱引用已被 GC 处理的条目。
回答by ceving
You need two maps: one which maps between the cache key and weak referencedvalues and one in the opposite direction mapping between the weak referenced values and the keys. And you need a reference queueand a cleanup thread.
您需要两个映射:一个映射在缓存键和弱引用值之间,另一个映射在弱引用值和键之间的相反方向。你需要一个引用队列和一个清理线程。
Weak references have the ability to move the reference into a queue when the referenced object can not accessed any longer. This queue has to be drained by a cleanup thread. And for the cleanup it is necessary to get the key for a reference.This is the reason why the second map is required.
当被引用的对象无法再访问时,弱引用能够将引用移动到队列中。该队列必须由清理线程清空。 对于清理,有必要获取密钥以供参考。这就是为什么需要第二张地图的原因。
The following example shows how to create a cache with a hash map of weak references. When you run the program you get the following output:
以下示例显示了如何使用弱引用的哈希映射创建缓存。当你运行程序时,你会得到以下输出:
$ javac -Xlint:unchecked Cache.java && java Cache {even: [2, 4, 6], odd: [1, 3, 5]} {even: [2, 4, 6]}
The first line shows the contents of the cache before the reference to the odd list has been deleted and the second line after the odds have been deleted.
第一行显示了在对奇数列表的引用被删除之前缓存的内容,以及在奇数列表被删除之后的第二行。
This is the code:
这是代码:
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();
}
}