Hash-consing包括在内存中只保留给定对象的一个副本;也就是说,如果两个对象在语义上相等(相同的内容),那么它们应该在物理上相等(内存中的相同位置)。该技术通常通过保持全局哈希集并仅在它们不等于哈希集中的对象时创建新对象来实现。
另一个要求是,如果哈希表中的对象除了哈希表之外没有被任何引用,那么它们应该是可收集的。否则说,哈希表应包含弱引用。
由于需要有恒定的时间,因此需要进行浅层,哈希和等式测试,因此问题更加复杂;因此,对象具有唯一标识符,当将新对象添加到表中时,该标识符会递增。
我有一个使用System.Collections.Generic.Dictionary<key, node>
的工作实现,其中key
是一个元组,给出了一个浅层的节点摘要(适用于默认的散列和相等测试),node
是对象。唯一的问题是Dictionary
保留了对节点的强引用!
我可以使用Dictionary
到WeakReference
,但这不会释放指向悬空引用的键。
有些人主张使用System.Runtime.CompilerServices.ConditionalWeakTable
,但这个类似乎反过来了:它在收集密钥时释放了值,而我需要在收集值时释放密钥。
可以尝试使用System.Runtime.CompilerServices.ConditionalWeakTable<node, node>
,但我需要自定义哈希和相等测试...并且ConditionalWeakTable
记录而不是以使用GetHashCode()
虚方法而是使用默认的散列函数。
因此我的问题是:是否有一些等价的Dictionary
可以保留对值的弱引用并在引用变为悬空时释放键?
答案 0 :(得分:3)
你是对的,CWT没有解决散列问题,因为它引发了问题 - 它的键假设引用相等。但是,值得指出的是CWT不会保留密钥或值。这是一个小测试:
open System.Collections.Generic
open System.Runtime.CompilerServices
let big () =
ref (Array.zeroCreate (1024 * 1024) : byte [])
let test1 () =
let d = Dictionary(HashIdentity.Reference)
for i in 1 .. 10000 do
stdout.WriteLine(i)
let big = big ()
d.Add(big, big)
d
let test2 () =
let d = ConditionalWeakTable()
for i in 1 .. 10000 do
stdout.WriteLine(i)
let big = big ()
d.Add(big, big)
d
在我的计算机上,test1
内存不足,test2
成功。似乎只有在CWT没有保持键和值时才会发生这种情况。
对于散列,你最好的选择可能是Artem在评论中提出的建议。如果这听起来太复杂,那么让用户控制也很有意义,比如说:
let f = MyFactory() // a dictionary with weak reference values hidden inside
f.Create(..) : MyObject // MyObject has no constructors of its own
f.Cleanup() // explicitly cleans up entries for collected keys
然后你不需要引入线程,研究GC内部工作原理,或做任何魔术。库的用户可以决定清理或仅仅“忘记”工厂对象的位置 - 这将收集整个表格。