Java中hashCode和内存地址的差别

368 阅读3分钟

解释问题:为什么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不是内存地址,是通过算法生成的一个对象的标识,可以提高哈希表的执行效率。