【问题标题】:Any weak interning collections (for immutable objects)任何弱实习集合(对于不可变对象)
【发布时间】:2012-06-11 00:39:46
【问题描述】:

在某些涉及不可变对象的情况下,可能会出现许多语义相同的不同对象。一个简单的例子是将文件中的多行文本读入字符串。从程序的角度来看,两行具有相同的字符序列这一事实是“巧合”,但从程序员的角度来看,可能会出现大量重复。如果许多字符串实例是相同的,则将对这些不同实例的引用更改为对单个实例的引用将节省内存,并且还有助于它们之间的比较(如果两个字符串引用指向同一个字符串,则无需执行字符-逐字符比较以确定它们是否相同)。

在某些情况下,系统提供的字符串实习工具可能很有用。但是,它有几个严重的限制:

  1. 一旦一个字符串被实习,这个实习副本将永远存在,无论是否存在任何其他对它的引用
  2. 字符串实习工具仅适用于字符串,不能用于任何其他不可变类型。

如果存在一个真正的WeakDictionary<ImmutableClassType, ImmutableClassType>(对于每个元素,键和值都是相同的),代码可以执行以下操作:

if (theDict.TryGetValue(myString, ref internedString))
  myString = internedString;
else
  theDict[myString] = myString;

不幸的是,我不知道 .net 中有任何内置的 WeakDictionary<keyType, valType> 类。此外,为每个项目的键和值生成弱引用似乎很浪费,因为这两个引用总是指向同一个东西。

我读过一些关于ConditionalWeakTable 的文章,这听起来像是一个有趣的类,但我认为它在这里不可用,因为目标是能够获取一个实例并找到另一个独立的实例在语义上是等价的。

对于类的实例将具有明确定义的生命周期的情况,使用传统的Dictionary 来合并相同的实例可能是合理的。然而,在许多情况下,可能很难知道何时应该放弃这样的Dictionary 或清理其中的项目。基于WeakReference 的实习集合将避免此类问题。这样的事情是否存在,或者它是否可以很容易地实现?

附录 正如 svick 所指出的,Dictionary<WeakReference, WeakReference> 会有些问题,因为没有实用的方法来定义一个 IEqualityComparer,它会有一个实时的 WeakReference 返回其目标的 GetHashCode 值,并且有一个死的继续返回那个值。可以定义一个包含整数目标哈希码值(在构造函数中设置)的结构,并且其自己的GetHashCode 将返回该整数。稍有改进可能是使用ConditionalWeakTableWeakReference 的目标链接到可用于将表项排入队列以进行删除的可终结对象。

我不确定在急切地清理字典与采取更被动的方法(例如,如果自上次扫描以来至少有一次 GC,则在添加项目时执行扫描)之间的适当平衡是什么,并且自上次扫描以来添加的项目数超过了幸存的项目数)。遍历字典中的所有内容不会是免费的,但 ConditionalWeakTable 可能也不会是免费的。

PPS 我正在考虑的另一个概念,但我认为它可能不如弱实习方法有用,它是让一个逻辑上不可变的类型持有一个可变的“时间戳”值,并有一个接受它的比较方法ref 的论据。如果发现两个不同的实例相等,则将检查它们的时间戳值。如果两者都为零,则将从全局计数器(-1、-2、-3 等)为它们分配连续的负数。具有(或分配)较低时间戳值的参数将被另一个替换。

使用这种方法,如果许多对象实例被重复地相互比较,许多引用将被替换为对“旧”实例的引用。根据使用模式,这可能会导致大多数相同的对象实例在不使用任何类型的实习字典的情况下被合并。然而,对嵌套对象应用这种方法将需要“不可变”对象允许嵌套对象引用被变异以指向其他假定相同的嵌套对象。如果“假定相同”的对象总是存在,这应该没问题,但如果不是,可能会导致相当奇怪的不当行为。

【问题讨论】:

  • WeakReference(以及 .Net 4.5 WeakReference<T>)是类,所以在普通字典中对键和值使用相同的实例还不够吗?
  • @svik:使用Dictionary<WeakReference, WeakReference> 的问题是目标已被收集的WeakReference 不会消失。相反,它将继续使用大量系统资源,直到所有对它的引用都被销毁。
  • @svick:可以将字典包装在一个类中,该类将计算发生了多少分配,并清除任何目标已死的WeakReference,如果自上次扫描字典以来,在至少发生了一个集合,并且添加了与上次扫描后存在的一样多的元素。这种方法可以保证死的 WeakReference 的数量不会超过活跃的峰值数量的两倍,同时只是将 O(1) 添加到实习时间。不过,如果存在更好的集合类型,使用它似乎会更好。
  • @supercat - 非常有趣的帖子。我自己一直在寻找非常相似的东西。您最终是否实施了适合您目的的解决方案?

标签: .net immutability weak-references string-interning


【解决方案1】:

您可以使用自定义相等比较器创建类似Dictionary<WeakReference, WeakReference> 的内容,并在适当的时候修剪那些不再存在的内容。一个问题是如何从字典中删除一个死的WeakRefrence,因为你不能通过引用相等(记住,你必须使用自定义相等比较器)或索引来删除它。可能的解决方案是创建一个继承自 WeakReference 的类型,并且即使引用已失效,它也具有正确的哈希码。或者您可以将其包装在自定义 struct 中。

但正如你所说,如果每个引用在它死后立即从字典中删除,那就太好了。我认为这样做的唯一方法是以某种方式使用终结器。但是如果你不想(或不能)修改字典中的类型,这将变得相当复杂。

基本思想是您将拥有与上述相同的弱引用字典(关于如何删除项目的警告仍然适用),但您还使用 @987654325 将带有终结器的辅助对象附加到字典中的每个项目@。在该终结器中,您将从字典中删除该项目。由于ConditionalWeakTable 的工作原理,如果字典中的某个项目被 GCed,附加的对象也会被 GC,这意味着它的终结器将被调用,因此该项目将从字典中删除

【讨论】:

  • 感谢您的来信。我刚刚写了一个回复作为我的问题的附录。
猜你喜欢
  • 1970-01-01
  • 2011-01-14
  • 1970-01-01
  • 2018-04-17
  • 1970-01-01
  • 2022-07-05
  • 2018-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多