Java
中的hash
值主要是用来在散列存储结构中确定对象的存储地址的,提高对象的查询效率。
Java
设计的顶级父类Object
类中,有两个方法很特殊,它们分别是equals
方法与hashCode
方法。——一旦重写了equals
方法,就一定要重写hashCode
方法。以下是Object
的源码:
public class Object {
/*
* Note that it is generally necessary to override the {@code hashCode}
* method whenever this method is overridden, so as to maintain the
* general contract for the {@code hashCode} method, which states
* that equal objects must have equal hash codes.
*/
public boolean equals(Object obj) {
return (this == obj);
}
/*
* The general contract of {@code hashCode} is:
*
* - Whenever it is invoked on the same object more than once during
* an execution of a Java application, the {@code hashCode} method
* must consistently return the same integer, provided no information
* used in {@code equals} comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
*
- If two objects are equal according to the {@code equals(Object)}
* method, then calling the {@code hashCode} method on each of
* the two objects must produce the same integer result.
*
- It is not required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the {@code hashCode} method on each of the
* two objects must produce distinct integer results. However, the
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hash tables.
*
*
* As much as is reasonably practical, the hashCode method defined by
* class {@code Object} does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the
* Java™ programming language.)
*
*/
public int hashCode() {
return identityHashCode(this);
}
/* package-private */ static int identityHashCode(Object obj) {
int lockWord = obj.shadow$_monitor_;
final int lockWordStateMask = 0xC0000000; // Top 2 bits.
final int lockWordStateHash = 0x80000000; // Top 2 bits are value 2 (kStateHash).
final int lockWordHashMask = 0x0FFFFFFF; // Low 28 bits.
if ((lockWord & lockWordStateMask) == lockWordStateHash) {
return lockWord & lockWordHashMask;
}
return identityHashCodeNative(obj);
}
}
如果一个类没有重写equals(Object obj)
方法,则等价于通过==
比较两个对象,即比较的是对象在内存中的空间地址是否相等;如果重写了equals(Object obj)
方法,则根据重写的方法内容去比较相等,返回true
则相等,false
则不相等。
equals
方法注释中的大致意思是:当我们将equals
方法重写后有必要将hashCode
方法也重写,这样做才能保证不违背hashCode
方法中“相同对象必须有相同哈希值”的约定。
hashCode
方法本质就是一个哈希函数,Object
类的作者在注释的最后一段的括号中写道:将对象的地址值映为integer
类型的哈希值。
提到hashCode
,就会想到哈希表,将某一对象的值映射到表中的某个位置,从而达到以O(1)
的时间复杂度来查询该对象的值。hashCode
是一个native
方法,哈希值的计算利用的内存地址。哈希表在一定程度上也可以起到判重的作用,但也可能存储哈希冲突,即使是两个不同的对象,它们的哈希值也可能是相同的。因此,虽然哈希表具有优越的查询性能,但也可能存在哈希冲突。
hashCode
方法注释中列了个列表,列表中有三条注释,当前需要理解的大致意思如下:
hashCode
方法,应当返回相同的integer
(哈希值);equals
方法判定为相等,那么就应当返回相同integer
。hashCode
方法不要求返回不相等的integer
,但是要求拥有两个不相等integer
的对象必须是不同对象。图中存在两种独立的情况:
因此,equals
方法与hashCode
方法根本就是配套使用的。对于任何一个对象,不论是使用继承自Object
的equals
方法还是重写equals
方法。hashCode
方法实际上必须要完成的一件事情就是,为该equals
方法认定为相同的对象返回相同的哈希值。如果只重写equals
方法没有重写hashCode
方法,就会导致``equals`认定相同的对象却拥有不同的哈希值。
Object
类中的equals
方法区分两个对象的做法是比较地址值,即使用==
。如果根据业务需求改写了equals
方法的实现,那么也应当同时改写hashCode
方法的实现。否则hashCode
方法依然返回的是依据Object
类中的依据地址值得到的integer
哈希值。
代入到具体的例子 –String
类,在String
类中,equals
方法经过重写,具体实现源码如下:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = length();
if (n == anotherString.length()) {
int i = 0;
while (n-- != 0) {
if (charAt(i) != anotherString.charAt(i))
return false;
i++;
}
return true;
}
}
return false;
}
}
通过源码可以看到,String
对象在调用equals
方法比较另一个对象时,除了认定相同地址值的两个对象相等以外,还认定对应着的每个字符都相等的两个String
对象也相等,即使这两个String
对象的地址值不同(即属于两个对象)。
String
类中对equals
方法进行重写扩充了,但是如果此时我们不将hashCode
方法也进行重写,那么String
类调用的就是来自顶级父类Obejct
类中的hashCode
方法。即,对于两个字符串对象,使用它们各自的地址值映射为哈希值。 也就是会出现如下情形:
也就是说,被String
类中的equals
方法认定为相等的两个对象拥有两个不同的哈希值——因为它们的地址值不同。
为什么重写equals
方法就得重写hashCode
方法?—— 因为必须保证重写后的equals
方法认定相同的两个对象拥有相同的哈希值。同时我们也得出了——hashCode
方法的重写原则就是保证equals
方法认定为相同的两个对象拥有相同的哈希值。
equals
里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode
进行对比,则只要生成一个hash
值进行比较就可以了,效率很高,那么既然hashCode
效率这么高为什么还要使用equals
进行比较呢?
因为hashCode
并不是完全可靠,有时候不同的对象生成的hashcode
也会一样(hash
冲突),所以hashCode
只能说是大部分时候可靠,并不是绝对可靠。 所以可以得出:
equals
相等的两个对象,它们的hashCode
肯定相等,也就是用equals
对比是绝对可靠的;hashCode
相等的两个对象,它们的equals
不一定相等,也就是hashCode
不是绝对可靠的;所有对于需要大量并且快速的对比的话如果都用equals
去做显然效率太低,解决方式是,每当需要对比的时候, hashCode
去对比,这就用到了哈希表,能够快速的地位到对象的存储位置,如果hashCode
不一样,则表示这两个对象肯定不相等(也就是不必再用equals
去再对比了),如果hashCode
相同,此时再对比它们的 equals
,如果equals
也相同,则表示这两个对象是真的相同了。
查看字符串的hashCode
:
String str = new String("abc");
System.out.println(str.hashCode()); // 96354
str += "a";
System.out.println(str.hashCode()); // 2987071
str += "b";
System.out.println(str.hashCode()); // 92599299
String str1 = "abcab";
System.out.println(str.hashCode() + " " + str1.hashCode()); // 92599299 92599299
String str2 = "ab";
String str3 = new String("ab");
System.out.println(str2.hashCode() + " " + str3.hashCode()); // 3105 3105
以下是String.hashCode
的源码:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private int hash; // Default to 0
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
*
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
*
* using {@code int} arithmetic, where {@code s[i]} is the
* ith 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]
,其中s
是字符串对应的char
数组,所以字符串内容一样的String
对象,调用hasCode()
返回值是一样的。但是反过来,当hashCode
返回的值一样时,其字符串内容不一定一样,因为上面的方法计算hash
时可能会发生位数溢出。
https://zhuanlan.zhihu.com/p/50206657
https://blog.csdn.net/weixin_64735186/article/details/122727751