From 069ad79a00498ceae34d946403f458ebf1d71c7a Mon Sep 17 00:00:00 2001 From: joyang1 Date: Mon, 19 Aug 2019 15:47:51 +0800 Subject: [PATCH] finish Article 9 for Effective Java --- docs/effective-java.md | 52 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/docs/effective-java.md b/docs/effective-java.md index 3e17a5c..a512525 100644 --- a/docs/effective-java.md +++ b/docs/effective-java.md @@ -1001,4 +1001,54 @@ map.put(new PhoneNumber(21, 210, 20000), "tommy"); 为 0,则整个散列值将不受这些初始域的影响,因为这些初始域会增加冲突的可能性。值 17 则是任选的。 步骤 2.b 中的乘法部分使得散列值依赖于域的顺序,如果一个类包含多个相似的域,这样的乘法运算就会产生一个更好的散列函数。例如, -如果 String 散列函数省略了这个乘法部分,那么只是字母顺序不同的所有字符串都会有相同的散列码。 \ No newline at end of file +如果 String 散列函数省略了这个乘法部分,那么只是字母顺序不同的所有字符串都会有相同的散列码。之所以选择 31,是因为它是一个 +奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会丢失。因为与 2 相乘等价于移位运算,使用素数的好处并不明显,但是习惯上都是 +使用素数来计算散列结果。31 有个很好的特性,即用移位和减法来替代乘法,可以得到更好的性能:31 * i == (i << 5) - 1。现代 +JVM 可以自动完成这种优化。 + +用上述方法,我们重写 hashCode 方法: + +```java + +@Override +public int hashCode() { + int res = 17; + res = 31 * res + areaCode; + res = 31 * res + prefix; + res = 31 * res + lineNumber; + return res; +} + +``` + +实际上,对于 PhoneNumber 类的 hashCode 实现而言,上面这个方法是非常合理的,相当于 JDK 中的实现。它的做法非常简单,也相当 +快捷,恰当地把不相等的电话号码分散到不同的散列桶中。 + +如果一个类是不可变的,并且计算散列码的开销的也比较大,就应该考虑把散列码缓存在对象内部,而不是每次请求的时候都重新计算散列码。 +如果你觉得这种类型的大多数对象会被用做散列键(hash keys),就应该在创建实例的时候计算散列码。否则,可以选择 “延迟初始化(`lazily initialize`)” 散列码,一直到 hashCode 第一次被调用的时候才初始化(见 71 条)。如果是 PhoneNumber 会被 +经常用来作为 hash key 的话,那么应该这样实现: + +```java + +// Lazily initialized, cached hashCode +private volatile int hashCode; + +@Override +public int hashCode() { + int res = hashCode; + if (res == 0) { + int res = 17; + res = 31 * res + areaCode; + res = 31 * res + prefix; + res = 31 * res + lineNumber; + } + + return res; +} + +``` + +**不要试图从散列码计算中排除掉一个对象的关键部分来提高性能**。虽然这样得到的散列函数运行起来可能会更快,但是它的效果不见得会更好,可能会导致散列表慢到根本无法使用。 + +Java 平台类库中的许多类,比如 String、 Integer 和 Date,都可以把它们的 hashCode 方法返回的确切值规定为该实例值的一个函数。一般来说,这并不是一个好主意,因为这样做严格地限制了在将来的版本中改进散列函数的能力。如果没有规定散列函数的细节,那么当你 +发现了它的内部缺陷时,就可以在后面的发行版本中修正它,确信没有任何客户端依赖于散列函数返回的确切值。