HashMap为什么不会出现 负数hashCode

248 阅读2分钟

1. HashCode 可能是负数

hashCode() 方法的返回值是 int 类型,范围是 -2^31 ~ 2^31-1,因此它可能是负数。例如:

String str = "negative";
System.out.println(str.hashCode()); // 输出: -1908435444(负数)

2. HashMap 如何处理负数的 HashCode

HashMap 内部通过以下步骤将 hashCode 转换为数组下标:

(1)扰动函数(Hash Function)

  • HashMap 会对 hashCode 进行扰动处理,以减少哈希冲突。

  • 扰动函数的实现如下:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    • h >>> 16 是将 hashCode 的高 16 位右移到低 16 位。
    • ^ 是异或操作,目的是将高 16 位和低 16 位混合,增加哈希值的随机性。

(2)计算数组下标

  • 通过扰动后的哈希值计算数组下标:

    int index = (n - 1) & hash;
    
    • n 是数组的长度(必须是 2 的幂,例如 16、32、64 等)。
    • & 是按位与操作,等价于 hash % n,但效率更高。

(3)为什么不会报错?

  • (n - 1) & hash 的结果一定是非负的,因为 n - 1 是一个二进制全为 1 的数(例如 n = 16 时,n - 1 = 15,二进制为 1111)。
  • 按位与操作会截取 hash 的低位,结果范围是 0 到 n - 1,不会出现负数。

3. 示例

假设 hashCode 为负数,n = 16(数组长度):

int hashCode = "negative".hashCode(); // 假设 hashCode = -1908435444
int hash = hashCode ^ (hashCode >>> 16); // 扰动处理
int index = (16 - 1) & hash; // 计算下标

步骤分解

  1. hashCode 是负数,例如 -1908435444

  2. 扰动处理后,hash 仍然可能是负数。

  3. 计算下标时:

    • n - 1 = 15,二进制为 0000 0000 0000 0000 0000 0000 0000 1111
    • hash & 15 会截取 hash 的低 4 位,结果范围是 0 到 15

4. 为什么用 & 而不是 %

  • 效率& 是按位与操作,比 %(取模)操作更快。
  • 限制& 要求数组长度 n 必须是 2 的幂,而 HashMap 保证了这一点。

~~Summary

  • hashCode 可能是负数,但 HashMap 通过扰动函数和按位与操作将其转换为非负的数组下标。
  • 计算下标的方式是 index = (n - 1) & hash,其中 n 是数组长度(2 的幂)。
  • 这种方式既高效又安全,不会导致数组越界或报错。