解释问题:为什么hashCode方法返回的hash值不是内存地址
从一段实例代码出发:
class Test {
public static void main(String[] args){
String str1 = new String("string");
String str2 = "string";
System.out.println("str1.hashCode:"+str1.hashCode());
System.out.println("str2.hashCode:"+str2.hashCode());
System.out.println("str1.hashCode==str2.hashCode:"+(str1.hashCode()==str2.hashCode()));
System.out.println("str1 == str2:" + str1==str2);
}
}
// 输出结果:
str1.hashCode:-891985903
str2.hashCode:-891985903
str1.hashCode==str2.hashCode:true
str1 == str2:false
我们都知道,==运算符比较的是内存地址,str1和str2内存地址不相等,所以结果为false是肯定的,但是为什么它们的hashCode却是相等的?
hashcode介绍
hashcode是Object中的函数,所有类都拥有的一个函数,主要返回每个对象的hash值,主要用于哈希表中,如HashMap、HashTable、HashSet。 哈希码的通用约定如下:
- 在java程序执行过程中,在一个对象没有被改变的前提下,无论这个对象被调用多少次,hashCode方法都会返回相同的整数值。对象的哈希码没有必要在不同的程序中保持相同的值。
- 如果2个对象使用equals方法进行比较并且相同的话,那么这2个对象的hashCode方法的值也必须相等。
- 如果根据equals方法,得到两个对象不相等,那么这2个对象的hashCode值不需要必须不相同。但是,不相等的对象的hashCode值不同的话可以提高哈希表的性能。
hashCode()和内存地址的概念区分
hash值:hash值是通过哈希运算的出来的关于对象的标识
内存地址: 内存地址是对象所在内存区域的号码,就相当于房间号
我们可以进行一个类比:
hash值:可以类比一个人的生日,但是这个日子可能不只是他一个人的生日,但是对于某个圈子里的人来说,这个日期可能是他的代表
内存地址:可以类比一个人的身份证号,每个人都只有唯一的一个身份证号
从源码角度查看hashCode()方法的hash值是怎么产生的
Object类中的hashCode方法:
public native int hashCode();
Object类中的hashCode方法是Java虚拟机实现的,底层由C/C++实现,我们结尾分析
String类中的hashCode方法:
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方法是通过,字符数组进行一些运算得到的。通过源码可以了解到,只要两个字符串的值相等,内存地址就算不等,hashCode也一样。
HashMap类中的hashCode方法:
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
引用了Objects类中的方法,Objects类是一个专门为对象准备的工具类,不能实例化也不能被继承:
Objects类中的hashCode方法:
// Objects类的定义:使用了final使它不能被继承
public final class Objects {
// Objects类的初始化方法:使用private加上throw Exception确保类不被实例化
private Objects() {
throw new AssertionError("No java.util.Objects instances for you!");
}
public static int hashCode(Object o) {
return o != null ? o.hashCode() : 0;
}
}
Objects类中的hashCode方法还是使用的是Object类中的hashCode方法,我们现在就来走入JVM底层看看public native int hashCode();是怎么实现的吧:
native hashCode源码分析
先给结论:
java6、7默认是返回随机数 java8默认是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia’s xorshift scheme随机数算法得到的一个随机数 源码探索过程可以参考: fangjian0423.github.io/2016/03/12/…
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0 ;
if (hashCode == 0) {
// This form uses an unguarded global Park-Miller RNG,
// so it's possible for two threads to race and generate the same RNG.
// On MP system we'll have lots of RW access to a global, so the
// mechanism induces lots of coherency traffic.
value = os::random() ;
} else
if (hashCode == 1) {
// This variation has the property of being stable (idempotent)
// between STW operations. This can be useful in some of the 1-0
// synchronization schemes.
intptr_t addrBits = intptr_t(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
} else
if (hashCode == 2) {
value = 1 ; // for sensitivity testing
} else
if (hashCode == 3) {
value = ++GVars.hcSequence ;
} else
if (hashCode == 4) {
value = intptr_t(obj) ;
} else {
// Marsaglia's xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- we'll
// likely make this the default in future releases.
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
}
value &= markOopDesc::hash_mask;
if (value == 0) value = 0xBAD ;
assert (value != markOopDesc::no_hash, "invariant") ;
TEVENT (hashCode: GENERATE) ;
return value;
}
Object类中的hashCode方法是native方法,是C/C++实现的。返回值是对于内存地址相同的对象返回相同的hashCode值,对于内存地址不同的对象,也通过算法来保证不一样,但如果重写了hashCode方法就无法保证这个特性了
总结:
hashCode不是内存地址,是通过算法生成的一个对象的标识,可以提高哈希表的执行效率。