0%

Java 中的 hashCode

hashCode简介

hashCode是 jdk 根据对象的值和状态算出来的一个 int 型数字,即对象的哈希码值,代表了该对象在内存中的存储位置。

顶级父类 Object 提供获取 hashcode 的方法,调用的是本地的方法;

1
public native int hashCode();

Java 中的 hash 值主要用来干什么的?

hash 值主要是用来在散列存储结构(HashMap、HashTable、HashSet 等等)中确定对象的存储地址的,提高对象的查询效率,

常见类的hashcode

string

阅读 String 源码来分析:

1
2
3
4
5
6
7
8
9
10
11
12
    public int hashCode() {
int h = hash;// 主要是 String 对象是不可变的,可以使用一个变量存储起来,方便以后使用。
if (h == 0 && value.length > 0) {
char val[] = value;
// 计算每个字符的 ascii 参与到 hashcode 计算中,将前面计算的结果乘以 31 。
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

为什么要以 31 为权来计算 hashCode?

  1. 因为 31 是素数,素数跟其他数相乘,更容易产生唯一性,所以 hash 冲突会小;

  2. 相乘的时候,数字越大,结果也越大,很容易超出 int 值上限,31是一个大小适中的素数.

  3. 为什么不是 17 ,23等等,参考StackOverflow上最高票的答案参考答案

    The value 31 was chosen because it is an odd prime. If it were even and the multiplication overflowed, information would be lost, as multiplication by 2 is equivalent to shifting. The advantage of using a prime is less clear, but it is traditional. A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance: 31 * i == (i << 5) - i. Modern VMs do this sort of optimization automatically.

    解释说,因为乘31可以方便地优化为移位和减法,实际计算的是(i << 5) - i

Long

查看 Long.javahashCode() 方法,

1
2
3
public static int hashCode(long value) {
return (int)(value ^ (value >>> 32));
}

因为 Long 类型有 64 位,比 hash 的长度多了一倍,利用前 32 位 和后 32 位异或,尽可能的让更多的位置参与计算 hash 来保证唯一性。

重写 hashcode 和 equals

为什么要同时重写

首先了解默认情况下的 hashcode 和 equals 方法是什么样:

  • hashcode 根据内存地址换算出来一个值(jdk5以前);
  • equals 判断对象的内存地址是否一样;

但是大多数情况下,我们是需要判断它们的值是否是相等的情况。

Object.hashCode的通用约定(摘自《Effective Java》第45页

  1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,它必须始终如一地返回 同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同,即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致。
  2. 如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果。
  3. 如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。

如果只重写了equals方法而没有重写hashCode方法的话,则可能会违反第二条:相等的对象必须具有相等的散列码(hashCode)

比如我们用一个可变的对象作为 hashMap 的键,并且重写了 hashcode 和 equals 方法,当我把一对键值(可变对象为键)装进 hashMap 后,又去改变了键对象的某个属性(这个属性参与了 hashcode 的计算),然后就不能再用这个可变对象去操作已经插入到 hashMap 中的键值对了。

自定义hashCode

参考 IDEA 根据字段自动生成的 hashCode 和 equals 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ObjectDemo {
private String value1;
private String value2;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

ObjectDemo that = (ObjectDemo) o;

if (value1 != null ? !value1.equals(that.value1) : that.value1 != null) return false;
return value2 != null ? value2.equals(that.value2) : that.value2 == null;
}

@Override
public int hashCode() {
int result = value1 != null ? value1.hashCode() : 0;
result = 31 * result + (value2 != null ? value2.hashCode() : 0);
return result;
}
}


本文整理自

仅做个人学习总结所用,遵循CC 4.0 BY-SA版权协议,如有侵权请联系删除!