我一直在思考这个问题:在CLR或Java中如何实现Object.GetHashCode
?此方法的合同是,如果在同一对象实例上调用它,则应始终返回相同的值。
请注意,我在谈论GetHashCode()的默认实现。派生类不需要覆盖此方法。如果他们选择不这样做,那么它们本质上将具有引用语义:当在哈希表& c中使用时,默认等于“指针相等”。这意味着不知何故,运行时必须在整个生命周期内为对象提供一个常量哈希码。
如果我正在运行的机器是32位,并且如果对象实例从未在内存中移动,理论上可以返回对象的地址,重新解释为Int32。这样会很好,因为所有不同的对象都有不同的地址,因此会有不同的哈希码。
然而,这种方法存在缺陷,其中包括:
如果垃圾收集器将对象移动到内存中,则其地址会发生变化,违反协议的哈希码也会在对象的生命周期内与哈希码相同。
< / LI>在64位系统上,对象的地址太宽,无法容纳Int32。
由于托管对象倾向于与某个偶数幂2对齐,因此最底部的位始终为零。当哈希码用于索引到哈希表时,这可能会导致错误的分发模式。
在.NET中,System.Object
由同步块和类型句柄组成,所以哈希码不能在实例本身中缓存。不知何故,运行时能够提供持久的哈希码。怎么样? Java,Mono和其他运行时如何做到这一点?
答案 0 :(得分:9)
不,不是地址,不能与垃圾收集器移动对象一起使用。它直观简单,只要在生成后存储就可以是随机数。它 存储在对象syncblk中。该字段存储多个对象属性,如果需要存储多个此类属性,则将其替换为已分配的syncblk的索引。
.NET算法使用托管线程ID,以便线程不可能生成相同的序列:
inline DWORD GetNewHashCode()
{
// Every thread has its own generator for hash codes so that we won't get into a situation
// where two threads consistently give out the same hash codes.
// Choice of multiplier guarantees period of 2**32 - see Knuth Vol 2 p16 (3.2.1.2 Theorem A)
DWORD multiplier = m_ThreadId*4 + 5;
m_dwHashCodeSeed = m_dwHashCodeSeed*multiplier + 1;
return m_dwHashCodeSeed;
}
种子存储在每个线程中,因此不需要锁定。至少这是SSCLI20版本中使用的内容。不知道Java,我想它是相似的。
答案 1 :(得分:4)
作为JVM实现者,我可以说基本哈希码IS通常与对象的地址相关。它通常不是地址,而是以合理的方式对其进行一些修改。我们做魔术以确保hashCode在对象的生命周期内是稳定的(即使在GC中,即使对象移动等等)。
我强烈建议为要进行散列的所有对象实现一个特定类型的hashCode()。该Object实现它并不意味着它是您使用的理想选择。
答案 2 :(得分:0)
我不确定你的意思是“在CLR或Java中究竟是如何实现Object.GetHashCode?”。 Java的“public int hashCode()”具有类的作者应该为其定义hashCode()实现的契约。换句话说,它可能在不同类别之间变化很大。我怀疑.Net平台也是如此。
Javadoc for Object描述了一种类似于您的想法的方法: http://download.oracle.com/javase/1.4.2/docs/api/java/lang/Object.html#hashCode()
尽可能合理, class定义的hashCode方法 对象确实返回不同的整数 对于不同的对象。 (这是 通常通过转换实现 对象的内部地址 变成一个整数,但是这个 实施技术不是 JavaTM编程所要求的 语言。)
如果您为类定义了基于身份以外的其他内容的相等性,则此方法不适用。