关于Java中HashCode的一些思考

关于Java中HashCode的一些思考

最近在重写hashCode和equals方法时,突然很想知道hashCode是如何计算而来的。于是便下载了openJdk1.8的源码,结合网上一些博客学习了一下,并编写代码进行了测试和总结。

1. Object类中的hashCode()方法
    public native int hashCode();

这是一个natvie方法,当一个类没有重写hashCode方法时,默认调用的hashCode就是Object类中的hashCode。那么Object类中的hashCode是如何计算而来的呢?

2. 找到对应的native方法源码实现

源码环境:openjdk1.8

在源码目录:openjdk\jdk\src\share\native\java\lang\Object.c

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

hashCode由JVM_IHashCode指针来处理

在源码目录:openjdk\hotspot\src\share\vm\prims\jvm.cpp , 找到JVM_IHashCode指针

// java.lang.Object ///

JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle))
  JVMWrapper("JVM_IHashCode");
  // as implemented in the classic virtual machine; return 0 if object is NULL
  return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;
JVM_END

计算hashCode的方法为ObjectSynchronizer::FastHashCode

在源码目录:openjdk\hotspot\src\share\vm\runtime\synchronizer.cpp ,找到ObjectSynchronizer::FastHashCode方法

// Inflate the monitor to set hash code
  monitor = ObjectSynchronizer::inflate(Self, obj);
  // Load displaced header and check it has hash code
  mark = monitor->header();
  assert (mark->is_neutral(), "invariant") ;
  hash = mark->hash();
  if (hash == 0) {
    hash = get_next_hash(Self, obj);
    temp = mark->copy_set_hash(hash); // merge hash code into header
    assert (temp->is_neutral(), "invariant") ;
    test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);
    if (test != mark) {
      // The only update to the header in the monitor (outside GC)
      // is install the hash code. If someone add new usage of
      // displaced header, please update this code
      hash = test->hash();
      assert (test->is_neutral(), "invariant") ;
      assert (hash != 0, "Trivial unexpected object/monitor header usage.");
    }
  }
  // We finally get the hash
  return hash;

ObjectSynchronizer::FastHashCode方法的最后代码如上,从对象头中获取hash,如果没有就生成一个hash设置到对象头中,生成hash的方法为get_next_hash(Self, obj)

在源码目录:openjdk\hotspot\src\share\vm\runtime\synchronizer.cpp ,找到get_next_hash方法

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 = cast_from_oop<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 = cast_from_oop<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;
}

这里生成hash的方法主要有如下几种:

  1. hashCode == 0

    返回的是由伪随机生成器生成的一个随机数

  2. hashCode == 1

    返回的是对象的内存地址做了移位和异或运算后的结果数

  3. hashCode == 2

    返回的是不变的1

  4. hashCode == 3

    返回的递增的序列

  5. hashCode == 4

    返回的是对象的内存地址

  6. hashCode == 5

    返回的是一个与当前线程有关的随机数与其他三个固定值进行xorshift运算后的结果数

    在源码目录:openjdk\hotspot\src\share\vm\runtime\thread.cpp ,找到固定值

      // thread-specific hashCode stream generator state - Marsaglia shift-xor form
      _hashStateX = os::random() ;
      _hashStateY = 842502087 ;
      _hashStateZ = 0x8767 ;    // (int)(3579807591LL & 0xffff) ;
      _hashStateW = 273326509 ;
    

jdk1.8中默认使用的是hashCode == 5

在源码目录:openjdk\hotspot\src\share\vm\runtime\globals.hpp

  product(intx, hashCode, 5, "(Unstable) select hashCode generation algorithm")      
3. 测试hashCode的生成

测试环境:jdk1.8,测试代码如下:

public class HashCodeTest
{
    public static void main(String... args) {
        User user1 = new User("小明1", 18);//User类只有name和age 两个属性
        User user2 = new User("小明2", 18);
        System.out.println("小明1: hashcode: "+ user1.hashCode());
        System.out.println("小明2: hashcode: "+ user2.hashCode());
    }
}
  1. 测试默认情况

    两次结果分别是:
    在这里插入图片描述在这里插入图片描述

    同一个对象的两次的hashCode值相同

  2. 测试hashCode==0的情况:

    两次的结果分别是:
    在这里插入图片描述
    在这里插入图片描述
    ​ 同一个对象两次的hashCode都不相同

  3. 测试hashCode==1的情况:

    两次的结果分别是:
    在这里插入图片描述
    在这里插入图片描述
    ​ 同一个对象两次的hashCode值相同,修改堆的初始值后:
    在这里插入图片描述
    ​ hashCode发生了变化

  4. 测试hashCode==2的情况

    两次的结果分别是:
    在这里插入图片描述
    在这里插入图片描述
    hashCode值均为1

  5. 测试hashCode==3的情况:

    两次的结果分别是:
    在这里插入图片描述
    在这里插入图片描述
    ​ hashCode为递增的关系

  6. 测试hashCode==4的情况:

    两次的结果分别是:
    在这里插入图片描述
    在这里插入图片描述
    ​ 两次的hashCode值相同,修改堆的初始值后:
    在这里插入图片描述
    ​ hashCode发生了变化

  7. 测试hashCode==5的情况:

    两次的结果分别是:
    在这里插入图片描述
    在这里插入图片描述
    ​ 两次的hashCode值相同,修改堆的初始值后:
    在这里插入图片描述
    ​ hashCode值仍先相同。并且测试7与测试1返回的结果中hashCode是相同的,印证了上述中jdk1.8中默认使用的hashCode==5这种方式。

4. 总结

jdk1.8中hashCode的生成是与当前线程有关的随机数和其他三个固定值进行xorshift运算后的结果数。在自定义的类中,如果只重写equals方法而不重写hashCode方法,会出现两个属性均相同的对象,通过equals方法比较是相等的,但两个对象的hashCode值却不相同(因为根据hashCode==5的生成方式,hashCode的生成与对象中的属性无关)。这种情况在操作实现方式为哈希表的一些数据结构时可能就会出现问题,比如equals方法判定为相同的对象却因hashCode不同而被认为是不同的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值