千家信息网

如何用JAVA源码解析hashcode方法

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,这期内容当中小编将会给大家带来有关如何用JAVA源码解析hashcode方法,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。在开发过程中我们可能会经常接触到hashc
千家信息网最后更新 2026年11月01日如何用JAVA源码解析hashcode方法

这期内容当中小编将会给大家带来有关如何用JAVA源码解析hashcode方法,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

在开发过程中我们可能会经常接触到hashcode这个方法来生成哈希码,那么底层是如何实现的?使用时有何注意点呢?

hashcode() 方法底层实现

hashcode()是Object的方法:

@HotSpotIntrinsicCandidatepublic native int hashCode();

它是一个native的方法,并且被@HotSpotIntrinsicCandidate注解修饰,证明它是一个在HotSpot中有一套高效的实现,该高效实现基于CPU指令。

具体的实现参考源码synchronizer.cpp:

static inline intptr_t get_next_hash(Thread* self, oop obj) {  intptr_t value = 0;  if (hashCode == 0) {    value = os::random();  } else if (hashCode == 1) {    intptr_t addr_bits = cast_from_oop(obj) >> 3;    value = addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random;  } else if (hashCode == 2) {    value = 1;             } else if (hashCode == 3) {    value = ++GVars.hc_sequence;  } else if (hashCode == 4) {    value = cast_from_oop(obj);  } else {    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 &= markWord::hash_mask;  if (value == 0) value = 0xBAD;  assert(value != markWord::no_hash, "invariant");  return value;}

可以看出,根据hashcode这个全局变量的取值,决定用何种策略生成哈希值,查看globals.hpp来看是哪一种变量:

 experimental(intx, hashCode, 5, "(Unstable) select hashCode generation algorithm")

发现是一个experimental的 JVM 变量,这样的话,想要修改,必须添加额外的参数,如下所示:

-XX:+UnlockExperimentalVMOptions -XX:hashCode=2

并且,这个hashCode默认为5。

哈希值是每次hashcode()方法调用重计算么?

对于没有覆盖hashcode()方法的类,实例每次调用hashcode()方法,只有第一次计算哈希值,之后哈希值会存储在对象头的 标记字(MarkWord) 中。

如果进入各种锁状态,那么会缓存在其他地方,一般是获取锁的线程里面存储,恢复无锁(即释放锁)会改回原有的哈希值。

关于对象头结构,以及对象存储结构,感兴趣的话,可以参考:Java GC详解 - 1. 理解Java对象结构

-XX:hashCode=0 利用 Park-Miller 伪随机数生成器生成哈希值

if (hashCode == 0) {    value = os::random();}

调用 os 的 random 方法生成随机数。这个方法的实现方式是: os.cpp:

//初始seed,默认是1volatile unsigned int os::_rand_seed = 1;static int random_helper(unsigned int rand_seed) {  /* standard, well-known linear congruential random generator with   * next_rand = (16807*seed) mod (2**31-1)   * see   * (1) "Random Number Generators: Good Ones Are Hard to Find",   *      S.K. Park and K.W. Miller, Communications of the ACM 31:10 (Oct 1988),   * (2) "Two Fast Implementations of the 'Minimal Standard' Random   *     Number Generator", David G. Carta, Comm. ACM 33, 1 (Jan 1990), pp. 87-88.  */  const unsigned int a = 16807;  const unsigned int m = 2147483647;  const int q = m / a;        assert(q == 127773, "weird math");  const int r = m % a;        assert(r == 2836, "weird math");  // compute az=2^31p+q  unsigned int lo = a * (rand_seed & 0xFFFF);  unsigned int hi = a * (rand_seed >> 16);  lo += (hi & 0x7FFF) << 16;  // if q overflowed, ignore the overflow and increment q  if (lo > m) {    lo &= m;    ++lo;  }  lo += hi >> 15;  // if (p+q) overflowed, ignore the overflow and increment (p+q)  if (lo > m) {    lo &= m;    ++lo;  }  return lo;}int os::random() {  // Make updating the random seed thread safe.  while (true) {    unsigned int seed = _rand_seed;    unsigned int rand = random_helper(seed);    //CAS更新    if (Atomic::cmpxchg(&_rand_seed, seed, rand) == seed) {      return static_cast(rand);    }  }}

其中,random_helper 就是随机数的生成公式的实现,公式是: 这里,a=16807, c=0, m=2^31-1

由于这些随机数都是采用的同一个生成器,会 CAS 更新同一个 seed,如果有大量的生成的新对象并且都调用hashcode()方法的话,可能会有性能问题。重复调用同一个对象的hashcode()方法不会有问题,因为之前提到了是有缓存的。

-XX:hashCode=1或者4 基于对象指针 OOPs

OOPs(Ordinary Object Pointers)对象指针是对象头的一部分。关于对象头结构,以及对象存储结构,感兴趣的话,可以参考:Java GC详解 - 1. 理解Java对象结构。可以简单理解为对象在内存中的地址的描述。

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 addr_bits = cast_from_oop(obj) >> 3;    value = addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random;}else if (hashCode == 4) {    value = cast_from_oop(obj);}

cast_from_oop很简单,就是获取oop的实现基类oopDesc的指向地址(oopDesc描述了OOP的基本组成,感兴趣可以参考:Java GC详解 - 1. 理解Java对象结构):

template  inline T cast_from_oop(oop o) {  return (T)(CHECK_UNHANDLED_OOPS_ONLY((oopDesc*))o);}

当-XX:hashCode=4,直接用oop的地址作为哈希值。-XX:hashCode=1则是经过变换的,每次发生 Stop The World (STW)stw_random会发生改变,通过这个addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random变换减少哈希碰撞,让哈希值更散列化。

想更深入了解 Stop the world,可以参考:JVM相关 - SafePoint 与 Stop The World 全解(基于OpenJDK 11版本)

-XX:hashCode=2 敏感测试,恒定为1

else if (hashCode == 2) {    value = 1;            // for sensitivity testing}

主要用于测试某些集合是否对于哈希值敏感。

-XX:hashCode=3 自增序列

else if (hashCode == 3) {    value = ++GVars.hc_sequence;}struct SharedGlobals {  // omitted  DEFINE_PAD_MINUS_SIZE(1, DEFAULT_CACHE_LINE_SIZE, sizeof(volatile int) * 2);  // Hot RW variable -- Sequester to avoid false-sharing  volatile int hc_sequence;  DEFINE_PAD_MINUS_SIZE(2, DEFAULT_CACHE_LINE_SIZE, sizeof(volatile int));};static SharedGlobals GVars;

每创建一个新对象,调用哈希值,这个自增数+1,可以看出,散列性极差,很容易哈希碰撞。

-XX:hashCode=5 默认实现

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

采用的算法是 Marsaglia's xor-shift 随机数生成法。主要是这篇论文提出的一种快速并且散列性好的哈希算法。

特殊的哈希值导致某些场景的问题

我们经常使用某个对象或者某个字段的哈希值,通过对于某个数组长度取模,获取到下标,取出数组对应下标的对象,进行进一步处理。这在负载均衡,任务调度,线程分配很常见。那下面这段代码是否有问题呢?

//获取userId这个字符串的哈希值的绝对值int index = Math.abs(userId.hashCode());//返回哈希值取模之后的下标的对象return userAvatarList.get(index % userAvatarList.size()).getUrl();

通常大多数情况下,是没有问题的,但是假设userId是这几个哈希值为Integer.MIN_VALUE的字符串:

System.out.println("polygenelubricants".hashCode());System.out.println("GydZG_".hashCode());System.out.println("DESIGNING WORKHOUSES".hashCode());

输出:

-2147483648-2147483648-2147483648

对于这些值,如果你用Math.abs()取绝对值的话,我们知道Math.abs(Integer.MIN_VALUE)还是等于Integer.MIN_VALUE,这是因为底层实现:

public static int abs(int a) {    return (a < 0) ? -a : a;}

-Integer.MIN_VALUE和Integer.MIN_VALUE是相等的。Integer.MIN_VALUE取模还是负数,这样取下标对应的对象的时候,就会报异常。

所以,需要修改为:

int index = Math.abs(userId.hashCode() % userAvatarList.size());return userAvatarList.get(index).getUrl();

上述就是小编为大家分享的如何用JAVA源码解析hashcode方法了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注行业资讯频道。

对象 哈希 方法 生成 结构 问题 随机数 参考 下标 存储 源码 兴趣 变量 地址 就是 底层 公式 内容 字符 字符串 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 中智农互联网科技 有格互联网科技 百单网互联网科技有限公司怎么样 伟宏互联网科技 互联网公司 科技公司 上海互联网科技有限公司 杭州互联网科技公司 互联网与未来科技 深圳天众互联网科技公司 互联网金融和金融科技 中盈科技互联网信息有限公司 银川新华互联网科技职业技术培训学校图片 互联网为啥属于高科技 燊美集互联网科技有限公司 允成互联网科技有限公司融资 滨湖区互联网智能科技产品有哪些 黑科技 体验场景 互联网大会 互联网科技年终奖 玉溪新华互联网科技怎么选 互联网对科技影响 互联网新科技 上海亿帐通互联网科技 镁科技互联网资讯平台 锡山区互联网智能科技产品供应 启辰互联网科技 广州有谷有贝互联网科技有限公司 上海乐五互联网科技有限公司 吉林省码上互联网科技电话 数字科技和互联网金融哪个好 恒生互联网科技趋势
0