【发布时间】:2016-03-05 00:51:07
【问题描述】:
我正在学习 Item 9, Effective Java [Always override hashcode() when you override equals]。
我对作者提出的观点有一些疑问:
- 作者说:
在步骤 1 中使用了一个非零初始值,因此哈希值将受到以下因素的影响 在步骤 2.a 中计算的哈希值为零的初始字段。如果使用零 作为步骤 1 中的初始值,整体哈希值将不受任何影响 这样的初始场,可能会增加碰撞。值 17 是任意的。
步骤 2.a 是:
对于您的对象中的每个重要字段 f(每个字段都纳入 通过 equals 方法计算帐户,即),请执行以下操作:计算 字段的 int 哈希码 c:
我。如果该字段是布尔值,则计算 (f ? 1 : 0)。
二。如果字段是 byte 、 char 、 short 或 int ,则计算 (int) f 。
三。如果字段是 long ,则计算 (int) (f^ (f >>> 32)) 。
四。如果字段是 float ,则计算 Float.floatToIntBits(f) 。
v.如果该字段是 double ,则计算 Double.doubleToLongBits(f) ,并且 然后按照步骤 2.a.iii 对结果进行哈希运算。
六。如果该字段是一个对象引用并且该类的 equals 方法通过递归调用 equals 来比较字段, 在字段上递归调用 hashCode。如果进行更复杂的比较 是必需的,为此字段计算“规范表示”并 在规范表示上调用 hashCode。如果值 字段为 null ,返回 0 (或其他一些常量,但 0 是 传统)。
七。如果该字段是一个数组,则将其视为每个元素都是 单独的字段。也就是说,计算每个显着的哈希码 元素通过递归应用这些规则,并组合这些值 每个步骤 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))。该值将如何帮助减少碰撞?
- 最后一段指出:
Java 平台库中的许多类,例如 String 、 Integer 和 Date ,在它们的规范中包含返回的确切值 通过他们的 hashCode 方法作为实例值的函数。这是 通常不是一个好主意,因为它严重限制了你的能力 在未来的版本中改进散列函数。如果您留下详细信息 散列函数未指定且发现缺陷或更好的散列 发现函数,您可以在后续的 发布,确信没有客户依赖返回的确切值 通过哈希函数。
他说hashCode返回的确切值是实例值的函数是什么意思?
提前感谢您的帮助。
【问题讨论】:
-
据我所知,“hashCode 方法作为实例值的函数”意味着生成的哈希码将取决于对象或实例的变量值。因此,具有相同值的两个对象可能会生成相同的 hashCode。这可能会导致碰撞。然后,需要实现一个更好的 hashCode 算法来消除冲突。
-
如果你查看 String hashcode() 生成方法的文档,他们实现了一个基于字符串中字符的公式。
-
这完全取决于您对哈希码的实际操作。最终,它是实现工作、运行时成本和您可以容忍的理想冲突数量之间的权衡。没有“更好”。如有疑问,请使用其中一种方便的哈希码构建器,例如番石榴,或者如果你真的不在乎,就返回 -1。或者,可以考虑使用 MD5 或 Murmur 在没有太多计算开销的情况下获得适当的值分布。我发现 String.hashCode() 和 HashMaps 很难扩展到超过几万个条目。
标签: java hashcode effective-java