Java的System.identityHashCode()在.Net中的对应物

4

Java的System.identityHashCode()方法

返回给定对象的哈希码,与该对象所属类是否重写hashCode()方法无关。

该哈希码基于对象标识,因此对于相同的对象,无论在调用identityHashCode()之间对象是否被修改,它始终是相同的。

除此之外,在一些Java运行时中,任何两个活动对象之间都不会发生哈希冲突:(这是Oracle在下面的源代码中的一种不准确的陈述,正如Jai的回答所示,以及另一个错误报告所指出的那样——这基本上使我的原始问题无效...)

[...] 垃圾对象可以被轻松回收并且地址空间可以被重新使用。冲突是由于地址空间的重用而导致的。如果原始对象保持活动状态(未被GC),则不会遇到此问题。

来源

在 .Net 中,有 RuntimeHelpers.GetHashCode(),它满足第一个条件,但不满足第二个条件:

请注意,GetHashCode 对于相等的对象引用始终返回相同的哈希码。然而,反之并不成立:相等的哈希码并不表示相等的对象引用。特定的哈希码值并不唯一地对应于特定的对象引用;不同的对象引用可以生成相同的哈希码。

那么,在 .Net 中是否有类似于 Java 的 identityHashCode() 的东西呢?

编辑:

有人认为这与 C# 中对象的内存地址 相同,但实际上不是,因为内存地址不能在此处(仅)使用,因为内存管理会移动对象,因此对象的生命周期内地址可能会发生变化。


1
Java并不保证不同的对象具有不同的哈希码。 - shmosel
1
保证并不等同于实现细节。 - mjwills
1
你为什么要删除我在你的帖子中添加的“我正在尝试构建一个稳定的排序算法”语句呢?它真的为你的问题提供了有用的背景信息。 - mjwills
1
我还怀疑您对Java的处理方式存在误解。它确保对象的哈希码不会改变(根据https://dev59.com/om865IYBdhLWcg3wd-ce)。但是,如果发生GC,内存被压缩并且对象被移动到其他位置,那么没有任何东西可以阻止该类型的新实例占用与“原始”对象相同的内存地址。然后,您将拥有两个具有相同哈希码的对象。因此,您不能声明“除此之外,任何两个活动对象之间都不会发生哈希冲突”。 - mjwills
1
虽然Java的Object#hashCode()System.identifyHashCode()保证了它返回给定实例的值永远不会改变,但它并没有提到它会为每个对象返回一个唯一的值。事实上,它只返回对象在堆中的末尾32位内存地址 - 这意味着0x 0 FFFF FFFF0x 1 FFFF FFFF都将返回0x FFFF FFFF。您可以修改错误报告的示例,例如创建一个静态列表来存储obj以防止GC,但仍然会发生冲突。 - Jai
显示剩余5条评论
3个回答

4

目前Java的Object#hashCode()System#identifyHashCode()不能保证返回唯一值。已经有相关问题,这里是一个例子。

您提到了一个错误报告,其中指出发生冲突是因为对象被垃圾回收,然后相同的内存地址被重用。但是,修改同样的测试用例将证明相反:

List<Object> allObjs = new ArrayList<>(); // Used to prevent GC
Set<Integer> hashes = new HashSet<Integer>(1024);

int colls = 0;
for (int n = 0; n < 100000; n++)
{
    Integer obj = new Integer(88);
    allObjs.add(obj); // keep a strong reference to prevent GC
    int ihash = System.identityHashCode(obj);
    Integer iho = Integer.valueOf(ihash);
    if (hashes.contains(iho))
    {
        System.err.println("System.identityHashCode() collision!");
        colls++;
    }
    else
    {
        hashes.add(iho);
    }
}

System.out.println("created 100000 different objects - "
        + colls
        + " times with the same value for System.identityHashCode()");

System.out.println("Size of all objects is " + allObjs.size());
System.out.println("Size of hashset of hash values is " + hashes.size());

结果:

System.identityHashCode() collision!
System.identityHashCode() collision!
System.identityHashCode() collision!
created 100000 different objects - 3 times with the same value for System.identityHashCode()
Size of all objects is 100000
Size of hashset of hash values is 99997

在所链接的SO问题中,还提到在某些JRE实现中,冲突率大大降低了。然而,似乎没有一种实现能够防止全部的冲突。因此,在Java中无法确保哈希码的唯一性。
因此,不要仅仅相信一个来源。评论这个问题的人也只是Oracle团队的成员,他或她很可能不是设计者。
在C#和Java中,你都需要创建自己的独特数字发生器。因此,NPras提供的解决方案似乎适用于.NET。

1
你让我不再相信Java可以防止碰撞。仔细想想,一个非碰撞的实现也不会真正快速,因为它需要线程同步等操作。 - Evgeniy Berezovsky
为了防止冲突,JVM必须防止您在其运行时内创建超过2³²个对象(即使每个对象只有1位)。这听起来也不是一个明智的想法,对吧?如果hashcode实现返回一个“long”,那么它可能会相当安全,也许可以持续10到20年? - Jai

1
我建议您参考来自C#语言设计和编译器团队的Eric Lippert所提出的以下答案,他建议使用ObjectIDGenerator来生成对象的唯一标识符。
引用源代码(好事他们现在开源了框架),它确实使用了RuntimeHelpers.GetHashCode(),但也通过分别存储引用来处理潜在的冲突。
请注意他关于对象生命周期的警告。如果您需要对短暂对象使用它,则建议重新实现生成器——现在您可以访问源代码,这变得更加容易了。

1
这可能是原帖作者最接近答案的地方了。但很难看出这对于实现“稳定”的排序有什么用处。 - mjwills
真的。但既然它已经被编辑掉了,那可能应该提出另一个问题 :) 此外,我不认为Java的identityHashCode会有所帮助。 - NPras
那是真的。 :) - mjwills
感谢NPras。然而,ObjectIDGenerator使用强引用,因此它永远不会释放内存。使用带有弱引用的缓存,例如WeakReference,就可以解决问题。 - Evgeniy Berezovsky
是的,那就是他建议的。使用弱引用重新实现这个类。 - NPras

0

长话短说:

在DotNet中,与Java的System.identityHashCode( Object obj )相当的是System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode( object obj )


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接