当前位置: 首页 > news >正文

HashMap的hash方法是如何实现的

在深入理解 HashMap 的整体设计之前,先看看它是如何将任意对象的 hashCode() 值,转换成数组索引可用的“桶下标”——也就是 HashMap 中的 hash(Object key) 方法。

/*** Computes key.hashCode() and spreads (XORs) higher bits of hash* to lower. Because the table uses power-of-two length, sets of* hashes that vary only in bits above the current mask will* always collide. (…若只用低位,则高位变化的 hash 会落到同一桶)*/
static final int hash(Object key) {int h;// key.hashCode() 可能是 int 全 32 位,(h = key.hashCode()) 先取其原始值// 然后将 h 右移 16 位,并与原 h 做异或,// 将高 16 位的信息“扩散”到低 16 位,增强低位的随机性return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

一、为什么要对 hashCode() 做“扰动”?

  1. hashCode() 分布不一定理想

    • 不同类、不同实现的 hashCode() 分布可能偏向高位或低位集中。

    • 如果直接用 hashCode() 的低几位作为桶下标,而这些位又缺乏随机性,就会导致大量键落到同一桶上,触发冲突。

  2. HashMap 桶数组长度总是 2 的幂

    • 在计算数组下标时,HashMap 用的是 (n - 1) & hash,相当于取 hash 的低 log₂n 位。

    • 举例:当数组长度 n = 16 时,只取 hash 的低 4 位。

    • 如果原始 hashCode() 的低 4 位一致,无论高位如何不同,都将冲突落到同一个桶。

为了避免以上问题,JDK 将 hashCode() 的高 16 位通过“右移并异或”的方式,混合进低 16 位。这样,即使原始 hashCode() 只在高位有所区别,也能反映到低位,降低冲突概率。


二、hash() 方法详解

static final int hash(Object key) {int h;// 如果 key 为 null,则直接返回 0(所有 null 键同样映射到桶 0)// 否则,先取 key.hashCode(),赋值给局部变量 h// 再计算 h ^ (h >>> 16)return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • h = key.hashCode()
    调用对象自身的 hashCode() 方法,获得一个 32 位的初始哈希值。

  • h >>> 16
    无符号右移 16 位,将高 16 位移到低 16 位的位置,并补 0。

  • h ^ (h >>> 16)
    把原始的 h 与右移后的 h 做位异或(XOR),混合了高位信息到低位,也保留了低位原有的信息。


三、示例:高位差异如何影响低位

int[] codes = {// 假设 key1.hashCode() = 0x123400010x12340001,// key2.hashCode() = 0x12350001,高位只差 0x000100000x12350001
};for (int hc : codes) {int mixed = hc ^ (hc >>> 16);System.out.printf("原始: 0x%08X, 混合后: 0x%08X\n", hc, mixed);
}

运行结果大致为:

原始: 0x12340001, 混合后: 0x12350001
原始: 0x12350001, 混合后: 0x12360001

可以看到,即使原始哈希只在第 17 位(0x00010000)有差异,经过扰动后,低 16 位也产生了不同,从而在取桶下标时会分散到不同的桶里。


四、与 JDK 7 的对比

  • JDK 7:在 HashMap 实现中,也做了扰动(supplementalHash()),但代码更复杂,且不同版本略有差异;

  • JDK 8:简化为上述的一行关键逻辑,更高效、更易理解。


五、为什么不扰动更多轮?

  • 性能权衡:每次扰动都要额外几条位运算指令,hash() 是高频调用,必需尽量精简;

  • 足够效果:一次 16 位混合已足以将高 16 位信息带入,极大改善低位分布;

  • 简单易维护:单次异或右移既高效又易于理解。


六、在实际使用中的影响

  1. 减少哈希冲突

    • 对于多数自定义 hashCode() 实现,都能更均匀地分布到桶中,保持接近 O(1) 性能。

  2. 抗 DoS 攻击

    • 即便攻击者构造一批只在高位或低位相同的 key,仍会被扰动分散到不同桶,降低碰撞风险。

  3. 兼容性

    • 对已有的 hashCode() 不做破坏性更改,只是在 HashMap 侧做一层灰盒优化,无需用户改动。


七、完整代码 & 注释

public class HashMap<K,V> {// ... 省略其他成员 …/** 阻止高碰撞:混合高 16 位至低 16 位 */static final int hash(Object key) {int h;// null 键恒定映射到桶 0if (key == null) {return 0;}// 调用 key.hashCode(),混合高 16 位信息,再返回h = key.hashCode();return h ^ (h >>> 16);}// 计算桶下标:取模 table.lengthstatic int indexFor(int hash, int length) {// length 必为 2^nreturn hash & (length - 1);}// put 操作示意public V put(K key, V value) {int hash = hash(key);int idx = indexFor(hash, table.length);// 在 table[idx] 链表或树中,查找或插入节点…}
}

八、总结

  • HashMap.hash() 方法通过一次右移异或,将 hashCode() 的高位信息“扩散”到低位,避免了电商、社交等高并发场景中因 hashCode() 分布不均导致的桶级拥堵;

  • 简洁高效的实现,兼顾性能与抗攻击性,是 Java 8 中 HashMap 能在绝大多数场景下保持稳定 O(1) 操作的重要一环。

相关文章:

  • Vue 3.0中自定义指令
  • 【Python socket模块深度解析】网络通信的核心工具
  • 使用pm2 部署react+nextjs项目到服务器
  • 【IC_Design】跨时钟域的寄存器更新后锁存
  • RK3588 RGA 测试
  • 解决leetcode第3548题.等和矩阵分割II
  • 推测解码算法在 MTT GPU 的应用实践
  • C++23 容器推导指引中对于分配器的非推导语境(P1518R2)
  • MCP协议:AI时代的“万能插座”,如何重塑互联网技术生态?
  • 【1004. 最大连续1的个数 III】
  • Redis进阶之高可用
  • 操作系统学习笔记第1章 操作系统概述(灰灰题库
  • SAR ADC 的常见架构
  • Spring Task
  • 结课作业自选01. 内核空间 MPU6050 体感鼠标驱动程序(二)(完整实现流程)
  • 服务器硬盘分类
  • 服务器磁盘按阵列划分为哪几类
  • 【Vue】将响应式对象转为非响应式对象
  • (37)服务器增加ipv6配置方法
  • 浪潮Inspur服务器产品线概述
  • 挂马网站教程/广州seo公司
  • 网站建设搭建环境/做网站推广的公司
  • 做网站镜像步骤/优化课程体系
  • 天猫商城官方网站/商城推广
  • 石家庄做网站需要多少钱/引擎搜索有哪些
  • 怎么做支付网站/seo主要做什么