在hashCode中的按位运算符“>>>”

5
我有两个相关的问题:
  1. 位运算符 >>> 的意思是将二进制数向右移动那么多位,同时在最高有效位填充0。但是,为什么以下操作会产生相同的数字:5>>>32得到5,-5>>>32得到-5。因为如果上述描述正确,那么这两个操作的最终结果都应该为0。

  2. 继续上面的话题,根据Effective Java书籍,在计算哈希码时(如果字段是长整型),我们应该使用(int)(f ^ (f >>> 32))。为什么我们要这样做,有什么解释吗?

3个回答

3

5可以表示为0101,如果你将它向右移动1位,即5>>>1,结果将会是0010=2

如果左操作数的提升类型为int,则只使用右操作数的最低5位作为移位距离。就好像右操作数被位逻辑AND运算符&(§15.22.1)与掩码值0x1f进行了处理一样。实际使用的移位距离因此始终在0到31之间,包括0和31。

当您使用<<或>>运算符移位整数时,移位距离大于或等于32时,您需要对32取模(换句话说,您需要屏蔽移位距离除低阶5位以外的所有位)。这可能非常反直觉。例如,对于每个整数i,(i>>32)==i。您可能期望它将整个数字向右移动,对于正数输入返回0,对于负数输入返回-1,但它不会;它只是返回i,因为(i<<(32&0x1f))==(i<<0)==i。


谢谢您的解释,但第二个问题呢?为什么我们应该使用 (int) (f ^ (f >>> 32)) 来计算 hashCode(根据Effective Java书籍)? - Gaurav
1
长整型是一个64位的二进制补码整数。有符号的长整型最小值为-2^63,最大值为2^63-1。在Java SE 8及更高版本中,您可以使用长整型来表示无符号的64位长整型,其最小值为0,最大值为2^64-1。无符号长整型的最小值为0,最大值为2^64-1。每次右移将数字除以2^K。实际上,您无法用Java原始类型表示数字2^63 = 9223372036854775808,因为该数字大于最大长整型,而长整型是最大的原始类型。 - AJ.
谢谢,根据avrilfanomar的回复,我认为(int)(f ^ (f >>> 32))的操作基本上是通过异或数字的前半部分和后半部分来实现可能唯一数字的一种方式,然后将其转换为int类型。 - Gaurav

2

我知道这个问题早已得到解答,但是我尝试了一个例子来获得更多的澄清,我想这也会对其他人有所帮助。

    long x = 3231147483648l;
    System.out.println(Long.toBinaryString(x));
    System.out.println(Long.toBinaryString(x >>> 32));
    System.out.println(Long.toBinaryString(x ^ (x >>> 32)));
    System.out.println(Long.toBinaryString((int) x ^ (x >>> 32)));

这将打印出-

101111000001001111011001011110001000000000

1011110000

101111000001001111011001011110000011110000

1001111011001011110000011110000

如@avrilfanomar所提到的,这个操作使用异或运算符将长整型的前32位与其他32位进行异或操作,并使用无符号右移运算符来帮助我们执行此操作。由于我们想在计算哈希码时使用这个长整型字段,直接将 long 强制转换为 int 将意味着只有高32位不同的long字段将对哈希码贡献相同的值。这可能意味着只有在这个字段不同的两个对象将具有相同的哈希码,并且它们将存储在同一个桶中(例如使用列表解决碰撞),这会影响基于哈希的集合的性能。因此,进行此操作。


2

您第一个问题的答案在这里:为什么1>>32等于1?

简而言之,第二个问题的答案是这种方式使用了整个长整型值(而不是其中的一部分),请注意这可能是最快的方法。


请您能否详细说明第二点,因为根据解释,在长整型的情况下,我们可以考虑0到63的值。那么,在计算hashCode时,为什么要将数字向左移32位并与其本身进行异或运算? - Gaurav
1
我们正在对长整型的前32位和后32位进行异或操作。这就是这里的魔法=) - Andrii Andriichuk

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