为什么Object需要hashCode()这个方法?用意何在?
学过数据结构的都知道,哈希表或散列表是一种查询速度比较快的数据结构。时间复杂度O(1)。在Java语言中,类Object中有一方法hashCode()
public native int hashCode();
但是,很少有人思考为什么?如果说两个数进行比较需要equals()方法这个是合情合理的,但是为啥需要hashCode()呢?假如说当数据量很大时,比如10万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题,尤其是对性能要求严格的系统。这个时候hashCode()能够减少equals()的比较次数,大大提升效率。我觉得Object类设计hashCode()来提升比较的效率。
为什么重写equals()方法必须重写hashCode()方法?
有时候在想,是不是可以直接根据hashCode()值判断两个对象是否相等吗?答案是否定的,因为不同的对象可能会生成相同的hashcode值。虽然不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等,如果两个对象的hashcode值不等,则必定是两个不同的对象。如果要判断两个对象是否真正相等,必须通过equals方法。也就是说对于两个对象,
- 如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
- 如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
- 如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
- 如果两个对象的hashcode值相等,则equals方法得到的结果未知。
为什么hashCode()方法实现用的31这个质数?
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
我们可以查看一下String类的hashCode()源码,发现有如下的一个公式:
s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
那么,为什么是31呢?大概有如下原因:
31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!。(减少冲突)
31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)
选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突,具体哈希冲突的解决策略大家看看网上查查)
并且31只占用5 bits,相乘造成数据溢出的概率较小。
/** * @param radix 进制 eg: 2进制 * @param a 二进制字符串数组 eg: {1,1,0,0} * @return */ private static int calHashCode(int radix,int[] a){ int sum = 0; for(int i=0;i<a.length;++i){ sum = sum*radix + a[i]; } return sum; }
为什么hashCode()依赖于对象中易变的数据时需要当心?
对于依赖对象的易变数据,当然需要当心,不然你会出错的。在Effactive java中有如下一段话:
在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。
如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。
如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。
在Java编程思想中同样有一句话:
“设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果将一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码”。
在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段。
下面给出一个示例:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
//result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
// if (age != other.age)
// return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
Person p = new Person("DengYu", 30);
System.out.println(p1.hashCode());
HashMap<Person, Integer> hashMap = new HashMap<Person, Integer>();
hashMap.put(p, 1);
p.setAge(31);
//在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段。
System.out.println(hashMap.get(p));
System.out.println(hashMap.get(new Person("DengYu",30)));
}
}
参考文章
1浅谈Java中的hashcode方法 海子 (https://www.cnblogs.com/dolphin0520/p/3681042.html) http://www.cnblogs.com/dolphin0520/p/3681042.html
2 如何重写hashCode()和equals()方法 王鸿飞 https://blog.csdn.net/neosmith/article/details/17068365
3为什么在定义hashcode时要使用31这个数呢? steveguoshao https://blog.csdn.net/steveguoshao/article/details/12576849
4 为什么在定义hashcode时要使用31这个数呢?小M的专栏 https://blog.csdn.net/mingli198611/article/details/10062791
5 Java之音 http://www.javazhiyin.com/?p=513