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; // 计算下标
步骤分解
-
hashCode是负数,例如-1908435444。 -
扰动处理后,
hash仍然可能是负数。 -
计算下标时:
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 的幂)。 - 这种方式既高效又安全,不会导致数组越界或报错。