设计hashCode方法Java

时间:2015-12-01 07:13:48

标签: java hashcode effective-java

我正在研究第9项,有效Java [当你重写equals时总是覆盖hashcode()]。

我对作者提出的观点有一些疑问:

  1. 作者说:
  2.   

    在步骤1中使用非零初始值,因此哈希值将受到影响   初始字段,其在步骤2.a中计算的散列值为零。如果使用零   作为步骤1中的初始值,整体哈希值不受任何影响   这样的初始字段,可能会增加冲突。值17是任意的。

    步骤2.a是:

      

    对于对象中的每个重要字段f(每个字段都被接​​收到   通过equals方法的帐户,即),执行以下操作:a。计算   字段的int哈希码c:

         

    我。如果该字段是布尔值,则计算(f?1:0)。

         

    II。如果字段是byte,char,short或int,则为compute(int)f。

         

    III。如果该字段是long,则计算(int)(f ^(f>>>> 32))。

         

    IV。如果该字段是浮点数,请计算Float.floatToIntBits(f)。

         

    诉如果该字段是double,则计算Double.doubleToLongBits(f)和   然后在步骤2.a.iii中散列生成的long。

         

    VI。如果该字段是对象引用和此类的   equals方法通过递归调用equals来比较字段,   在该字段上递归调用hashCode。如果比较复杂一点   是必需的,为此字段计算“规范表示”   在规范表示上调用hashCode。如果是的价值   field为null,返回0(或其他一些常量,但0为   传统)。

         

    七。如果该字段是数组,则将其视为每个元素都是a   单独的领域。也就是说,计算每个重要的哈希码   元素通过递归应用这些规则,并组合这些值   每步2.b.如果数组字段中的每个元素都很重要,那么   可以使用版本1.5中添加的Arrays.hashCode方法之一。

    假设结果计算如下:

    result = 31 * result + areaCode;      
    result = 31 * result + prefix;
    result = 31 * result + lineNumber;
    

    如果结果的初始值为0且上面的所有给定字段都为0,则结果将保持为0。 但是,即使结果最初不为0,每当初始字段为0时,结果将达到相同的常量,这将是: 31 *(31 *(31 * 17))。这个值如何帮助减少碰撞?

    1. 最后一段说明:
    2.   

      Java平台库中的许多类,例如String,Integer   和日期,在其规格中包括返回的确切值   通过他们的hashCode方法作为实例值的函数。这是   通常不是一个好主意,因为它严重限制了你的能力   在将来的版本中改进哈希函数。如果你留下细节   未指定散列函数,找到缺陷或更好的散列   发现函数后,可以在后续更改哈希函数   发布,确信没有客户依赖于返回的确切值   通过哈希函数。

      他的意思是说hashCode返回的确切值是实例值的函数?

      提前感谢您的帮助。

5 个答案:

答案 0 :(得分:1)

见这个例子:

    String a = "Abc";
    String b = "Abc";
    String c = "Pqr";
    System.out.println(" "+a.hashCode()+" "+b.hashCode()+" "+c.hashCode());

输出:  65602 65602 80497

这清楚地表明字符串的hashCode()取决于值。

从hashCode()文档中提取:
int java.lang.String.hashCode()

返回此字符串的哈希码。 String对象的哈希码计算为

s [0] * 31 ^(n-1)+ s [1] * 31 ^(n-2)+ ... + s [n-1]

使用int算术,其中s [i]是字符串的第i个字符,n是字符串的长度,^表示取幂。 (空字符串的哈希值为零。)

答案 1 :(得分:1)

这个值如何帮助减少碰撞?

散列冲突主要通过整个散列范围(这里是整数类型)的良好分布来实现。

通过将0定义为计算哈希结果的初始值,您可以在较小范围内进行一定程度的限制。以微小方式不同的对象 - 可能仅在某些字段中 - 产生彼此相距不远的哈希码。这使哈希冲突更有可能发生。

通过定义一个非零的初始值,您只需增加计算的哈希码之间的差距,这些哈希码只能以次要的方式区分。因此,您可以更好地利用哈希范围,并且更有可能使哈希冲突更加不可能。

他的意思是说hashCode返回的确切值是实例值的函数?

它只是意味着您应该使用对象的值(即其字段的值)来计算哈希码。你已经在你的例子中做过了,我认为你已经暗中理解了它。

但是:Joshua Bloch打算用这一段说些别的话:他想警告你记录哈希码计算的确切函数。如果您这样做,则限制自己在将来的版本中不再能够更改实现,因为某些用户可能期望特定的实现,并且您将根据您的代码破坏某些代码。

答案 2 :(得分:0)

Effective Java中的hashCode实现专门指示您为结果的初始值选择非零值。至于你的第二个问题,当用于等于对象比较的内部状态相同时,hashCode 假设产生相同的值。因此,当实例变量全为零时,您获得相同的值的事实将满足hashCode的约定。请注意,整个子标题是“覆盖equals时始终覆盖hashCode”。

答案 3 :(得分:0)

对于你的第一个问题,如果2个对象相等,它们应该返回相同的哈希值,这就是为什么当你覆盖equals方法时覆盖哈希方法是个好主意。它不会避免相等对象的碰撞,但是当对象相等时,它确实不太可能发生碰撞,这更重要,因为我们希望能够尽快找到唯一对象。

关于你的第二个问题,我并没有假装在设计Hash代码方面有很多经验,但我相信他的意思是某些对象只能返回一个Hash值(例如Singleton)。

他说将这个值放在文档中是一种不好的做法,因为您可能希望稍后更改哈希函数,或者哈希函数中的其他变量可能会在以后更改返回值时更改。

指定返回值或依赖指定的返回值都是一个坏主意。

答案 4 :(得分:0)

首先,我想说一个通常没有明确表达的非常重要的事情:

为MOST CASES实现哈希码并不重要。它只分解为性能问题。因此,如果您对哈希码和对象标识有问题,只需返回-1。你的表现会很糟糕但是实施得很可靠。但是,在您拥有数千个使用哈希码的对象之前,您将无法识别出糟糕的性能。顺便说一句:“碰撞”在哈希码的上下文中看起来像一个重要的词。是的,但只有表现确实是一个问题。哈希码值的“冲突”并不意味着您的程序无法正常工作。这意味着您的程序运行速度可能会变慢。因为使用键映射访问将导致对具有相同哈希码的对象进行连续迭代。在高性能环境中,这可能是一个问题。在大多数情况下没有。

但是如果你重写hashcode,那么重要的是:你必须正确地实现它。因此,应始终满足定义:如果equals返回true,则hashcode应返回相同的值。

另一件事:虽然你不小心不会遇到问题,但计算非不可变值的哈希码是一个坏主意。这是因为一旦使用了哈希码,该对象就被放置在“地图”中的特殊位置。如果值改变了哈希码所依赖的,则该对象可能会丢失或变得难以访问。这将影响您的计划的正确性。

结论:如果您确实需要性能,请仅使用哈希码。然后你应该确保你正确应用它。这里容易出错,但这些错误可能是最难识别的。