【问题标题】:How is Object.GetHashCode() implemented in CLR & JVM?在 CLR 和 JVM 中如何实现 Object.GetHashCode()?
【发布时间】:2011-07-31 16:49:26
【问题描述】:

我一直在思考这个问题:Object.GetHashCode 在 CLR 或 Java 中究竟是如何实现的?这个方法的约定是,如果在同一个对象实例上调用它,它应该总是返回相同的值。

请注意,我说的是 GetHashCode() 的默认实现。派生类不需要重写此方法。如果他们选择不这样做,他们本质上将具有引用语义:在哈希表 &c 中使用时,默认情况下相等等于“指针相等”。这意味着不知何故,运行时必须在对象的整个生命周期内为对象提供一个恒定的哈希码。

如果我运行的机器是 32 位的,并且对象实例从未在内存中移动,理论上可以返回对象的地址,重新解释为 Int32。这很好,因为所有不同的对象都有不同的地址,因此会有不同的哈希码。

但是,这种方法存在缺陷,其中包括:

    1234563 /li>
  • 在 64 位系统上,对象的地址太宽,无法放入 Int32。

  • 因为托管对象倾向于与 2 的某个偶数幂对齐,所以最底部的位将始终为零。当哈希码用于索引哈希表时,这可能会导致错误的分布模式。

在 .NET 中,System.Object 由一个同步块和一个类型句柄组成,仅此而已,因此哈希码不能缓存在实例本身中。不知何故,运行时能够提供持久的哈希码。如何? Java、Mono 和其他运行时如何做到这一点?

【问题讨论】:

    标签: mono jvm clr low-level


    【解决方案1】:

    我不确定您所说的“Object.GetHashCode 究竟是如何在 CLR 或 Java 中实现的?”。 Java 的“public int hashCode()”约定类的作者应该为其定义 hashCode() 实现。换句话说,不同类别之间的差异可能很大。我怀疑 .Net 平台也是如此。

    Object 的 Javadoc 描述了一种类似于您的想法的方法: http://download.oracle.com/javase/1.4.2/docs/api/java/lang/Object.html#hashCode()

    在合理可行的情况下, 类定义的 hashCode 方法 对象确实返回不同的整数 对于不同的对象。 (这是 通常通过转换实现 对象的内部地址 成一个整数,但这 实现技术不是 JavaTM 编程要求 语言。)

    如果您已将类的相等定义为基于身份以外的其他内容,则此方法不合适。

    【讨论】:

    • 从 Object 类派生时不需要实现 GetHashCode。如果不这样做,您就实现了引用语义。上面的 JavaDoc 暗示“通常”,对象的哈希码(以及任何不覆盖其 GetHashCode 实现的派生类)将返回对象的地址。如果 GC 移动了您的对象,您现在将拥有与 GC 之前不同的相同对象的哈希码。我猜这与 Hashtables 配合得不好。
    • 你说得对,我不是必须的,这就是为什么我在第二句中放了“应该”,而不是“必须”。 =) 我仍然认为这是一个很好的做法,就像 Trent Gray-Donald 在他的回答中所说的那样。
    【解决方案2】:

    不,不是地址,它不能与垃圾收集器移动对象一起使用。直观简单,可以是随机数,只要生成后存储即可。它确实存储在对象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,我想它是相似的。

    【讨论】:

    • 感谢您的回答,这很有意义。我的想法被锁定在同步块只能存储一个东西的想法上,但是您在此处绘制的机制解释了如何根据需要向对象添加多个额外属性。
    【解决方案3】:

    作为 JVM 实现者,我可以说基本哈希码通常与对象的地址相关。它通常不完全是地址,而是以合理的方式对其进行了一些修改。我们使用魔法来确保 hashCode 在对象的整个生命周期内都是稳定的(即使跨 GC,即使对象移动等)

    我强烈建议为您将要进行散列的所有对象实现一个良好的特定于类型的 hashCode()。该 Object 实现了它并不意味着它非常适合您使用。

    【讨论】:

    • 神奇地。说真的,我并不是要回避,但这有时是竞争优势的来源。我可以说的是,我们的 JVM 的某些版本在我们的标头的“哈希和标志”部分保留了位。这通常不是完整的 32 位哈希,因此使用了一些重复(并导致潜在熵的损失)。其他选项包括记住对象是否已被散列,因此如果需要移动对象,则记住“原始”散列。它可能会保存在某种外线数据结构中,或者可能保存在对象的另一部分中。
    猜你喜欢
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    • 2011-09-23
    • 1970-01-01
    • 2011-03-16
    • 1970-01-01
    • 2016-11-23
    相关资源
    最近更新 更多