Java数组HashCode实现

58

有点奇怪。一位同事问我在Java中如何实现myArray.hashCode(),我认为我知道,但之后我跑了几个测试。请查看下面的代码。我注意到的奇怪之处是,在我写第一个sys out时,结果不同。请注意,它几乎像是报告了一个内存地址,修改类移动了地址或其他什么东西。只是想分享一下。

int[] foo = new int[100000];
java.util.Random rand = new java.util.Random();

for(int a = 0; a < foo.length; a++) foo[a] = rand.nextInt();

int[] bar = new int[100000];
int[] baz = new int[100000];
int[] bax = new int[100000];
for(int a = 0; a < foo.length; a++) bar[a] = baz[a] = bax[a] = foo[a];

System.out.println(foo.hashCode() + " ----- " + bar.hashCode() + " ----- " + baz.hashCode() +  " ----- " + bax.hashCode());

// returns 4097744 ----- 328041 ----- 2083945 ----- 2438296
// Consistently unless you modify the class.  Very weird
// Before adding the comments below it returned this:
// 4177328 ----- 4097744 ----- 328041 ----- 2083945


System.out.println("Equal ?? " +
  (java.util.Arrays.equals(foo, bar) && java.util.Arrays.equals(bar, baz) &&
  java.util.Arrays.equals(baz, bax) && java.util.Arrays.equals(foo, bax)));
4个回答

104

java.lang.ArrayhashCode方法是从Object继承而来的,这意味着hashcode取决于引用。如果想要基于数组内容获取hashcode,请使用Arrays.hashCode

但要注意,这是一个浅层次的hashcode实现。还有一种深层次的实现:Arrays.deepHashCode


1
谢谢您的回答,但为什么java.lang.Array默认不覆盖hashCode(和toString)方法?有没有什么好的理由? - Krzysztof Kaczor
4
为了让 hashCode 起到实际作用(因为它主要用于避免昂贵的 .equals 方法调用),它需要具备快速性,即使是一个浅层次的数组 hashCode 也可能非常慢。一个基本上随机的 hashCode 并不会造成伤害,只是没有任何优势。权衡利弊。 - Torque
只有当equals()以相同的方式糟糕时,它才不会受到影响。通常,“基本上随机”的hashCode将是一个严重的问题,因为如果equals为true,则hashCode必须相同。一个常量比随机更好。 - Mark
我很久以前写了这个注释,所以我无法说出当时的想法,但我可能是在指系统哈希码,它将返回一个不基于对象字段(因此“本质上随机”)的数字,但在调用之间当然保持对象相同。我显然措辞不当。 - Torque
1
@Mark Yet .equals() 中的数组是“有问题的”,因为 bar.equals(baz) 使用引用相等。当人们使用 Arrays.equals(foo, bar) 确定相等性时,问题就出现了,然后 使用 Arrays.hashCode(foo) 以获得哈希码。如果您正在使用 java.util.Arrays 中的方法进行相等性比较,则需要对哈希码也使用它们。 - Daniel Martin

7

数组使用默认的哈希码,该哈希码基于内存位置(但它不一定是内存位置,因为它只是一个int,而所有内存地址都无法适应)。您还可以通过打印System.identityHashCode(foo)的结果来查看此内容。

仅当它们是相同的、完全相同的数组时,数组才相等。因此,通常情况下,仅当它们是相同的、完全相同的数组时,数组哈希码才会相等。


(并且对象在内存中移动,如果您查看哈希码,它们通常不像地址) - Tom Hawtin - tackline
对于Java的最近版本,JVM的默认行为甚至不是基于内存地址来生成身份哈希码。 - Stephen C

1

Object.hashCode() 的默认实现确实是返回对象的指针值,尽管这取决于具体实现。例如,64位 JVM 可能会取指针并将高低字一起异或。鼓励子类在有意义的情况下覆盖此行为。

然而,在可变数组上执行相等性比较是没有意义的。如果元素发生更改,则两者不再相等。为了维护同一个数组无论其元素发生什么变化都将始终返回相同的 hashCode 不变量,数组不会覆盖默认的 hashcode 行为。

请注意,java.util.Arrays 提供了 deepHashCode() 实现,用于基于数组内容而不是数组本身的身份进行哈希时非常重要。


1
现代虚拟机会在内存中移动对象。当前地址可能被用作种子,但结果需要被存储。 - Tom Hawtin - tackline
1
在内存中移动仍然不会导致hashCode的更改。 - Arun R

0

我同意使用java.util.Arrays.hashCode(或google guava的通用包装器Objects.hashcode),但请注意,如果您正在使用Terracotta,则可能会导致问题-请参见这个链接


Terracotta链接已经失效。 - Andy Turner

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