【发布时间】:2010-01-12 08:23:51
【问题描述】:
我有一个带有 Id 属性的类 Foo。我的目标是没有两个 Foo 实例同时具有相同的 Id。
所以我创建了一个工厂方法 CreateFoo,它使用缓存来为相同的 Id 返回相同的实例。
static Foo CreateFoo(int id) {
Foo foo;
if (!cache.TryGetValue(id, out foo)) {
foo = new Foo(id);
foo.Initialize(...);
cache.Put(id, foo);
}
return foo;
}
缓存实现为 Dictionary
class WeakDictionary<TKey, TValue> where TValue : class {
private readonly Dictionary<TKey, WeakReference> items;
public WeakDictionary() {
this.items = new Dictionary<TKey, WeakReference>();
}
public void Put(TKey key, TValue value) {
this.items[key] = new WeakReference(value);
}
public bool TryGetValue(TKey key, out TValue value) {
WeakReference weakRef;
if (!this.items.TryGetValue(key, out weakRef)) {
value = null;
return false;
} else {
value = (TValue)weakRef.Target;
return (value != null);
}
}
}
问题是 WeakReference 在其目标被垃圾回收后仍保留在字典中。这意味着需要一些策略来手动“垃圾收集”失效的 WeakReference,正如 @Pascal Cuoq 在What happens to a WeakReference after GC of WeakReference.Target 中所解释的那样。
我的问题是:压缩弱引用字典的最佳策略是什么?
我看到的选项是:
不要从字典中删除弱引用。 IMO 这很糟糕,因为缓存会在我的应用程序的整个生命周期中使用,并且很多死的 WeakReferences 会随着时间的推移而累积。
在每个 Put 和 TryGetValue 上遍历整个字典,并删除无效的 WeakReference。这在某种程度上违背了字典的目的,因为这两个操作都变成了 O(n)。
在后台线程中定期遍历整个字典。鉴于我不知道 CreateFoo 的使用模式,什么是一个好的间隔?
将每个插入的 KeyValuePair 附加到一个双端链表。每次调用 Put 和 TryGetValue 都会检查列表的头部。如果 WeakReference 是活动的,则将该对移动到列表的末尾。如果它已死,则从列表中删除该对并从字典中删除 WeakReference。
实现一个自定义哈希表,其细微差别是,当存储桶已满时,会先从存储桶中删除失效的 WeakReference,然后再照常进行操作。
还有其他策略吗?
最好的策略可能是具有摊销时间复杂度的算法。有这样的策略吗?
【问题讨论】:
-
C#/.NET 弱引用的严重缺陷之一是缺少来自 GC 的通信,例如 Java 将 ReferenceQueue 与弱引用关联起来。
-
dtb,当您对选项进行编号而不是项目符号时,讨论会变得更容易。
标签: c# .net-4.0 weak-references