HashMap 如何计算 hash 值?

HashMap 如何计算 hash 值?

这段代码是 HashMap 中用于计算哈希值的 hash() 方法。它的主要作用是对键(key)的原始哈希码进行二次处理,以减少哈希冲突的概率。

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  1. 输入:任意对象 key(允许为 null)。
  2. 输出:计算后的哈希值(int 类型)。
  3. 逻辑
    • 如果 keynull,直接返回 0(这是为了支持 null 键)。
    • 否则:
      1. 调用 key.hashCode() 获取原始哈希值 h
      2. h 无符号右移 16 位(即 h >>> 16),得到高 16 位的值。
      3. 将原始哈希值 h 与右移后的值进行 异或(XOR) 操作,得到最终哈希值。

为什么需要这样设计?

1. 问题背景

  • 当哈希表较小时(如容量为 16),键的哈希值通过取模运算(hash & (tableSize - 1))确定存储位置。若键的哈希值仅在高位不同(例如 Float 键的哈希码),而低位相同,会导致这些键被映射到同一位置,引发冲突。
  • 例如:Float1.02.0 的哈希码分别为 0x3F8000000x40000000。在表大小为 16 时,计算索引为 hash & 15,两者的低位均为 0,导致冲突。

2. 解决方案

  • 通过 h ^ (h >>> 16),将哈希值的高 16 位信息“混合”到低 16 位中。这样在计算索引时(hash & (tableSize - 1)),高位的信息也能参与运算,从而减少冲突。

3. 为什么用 XOR?

  • XOR 操作能均匀分布比特变化(比加法或乘法更简单且高效)。
  • 右移 16 位是为了让高位信息影响低位,同时保留部分原始哈希值的特性。

例子

假设 key.hashCode()0x12345678tableSize = 16

  1. 原始哈希值:00010010 00110100 01010110 011110000x12345678)。
  2. 右移 16 位:00000000 00000000 00010010 001101000x00001234)。
  3. 异或结果:
    00010010 00110100 01010110 01111000 (h)
    ^
    00000000 00000000 00010010 00110100 (h >>> 16)
    =
    00010010 00110100 01000100 010011000x1234444C)。
  4. 计算索引:0x1234444C & 15 = 0xC & 0xF = 12

如果没有混合高位,原始哈希的索引是 0x78 & 15 = 8,而混合后变为 12,避免了某些冲突。


总结

这段代码通过简单的位运算(XOR + 右移)将哈希值的高位信息扩散到低位,从而在哈希表大小是 2 的幂时减少冲突概率,同时保持高效性。

posted @ 2025-04-09 22:52  皮皮是个不挑食的好孩子  阅读(79)  评论(0)    收藏  举报