HashMap 对 key 有什么要求?hash 扰动做了什么?
结论先行:HashMap 对 key 的硬性要求是正确且一致地实现 equals 与 hashCode:equals 相等则 hashCode 必须相等,否则同一逻辑 key 会散落到不同桶、get 取不到值。hash 扰动是 h ^ (h >>> 16),把高 16 位信息混入低位,弥补“桶定位只看低位、容量又小”导致冲突偏多的缺陷。
对 key 的要求
| 要求 | 原因 |
|---|---|
| hashCode 稳定 | 扩容、重查时依赖同一 key 定位到同一桶 |
| equals 与 hashCode 一致 | 否则 equals 相同而桶不同,查不到 |
| 优先不可变类(String/Integer) | 可变字段参与 hashCode 会让 key 在桶内“漂移”成脏数据 |
| 允许 null | key 为 null 时 hash 视为 0,固定落入 0 号桶 |
扰动函数做了什么
桶位只取 (n - 1) & hash 的结果,即只依赖 hash 的低位;若不同 key 的 hashCode 低位相同就会连环冲突。h ^ (h >>> 16) 把高 16 位“异或折叠”进低 16 位,让低位也携带高位信息,冲突概率显著下降。
public class KeyDemo {
static final class Key {
private final String id;
Key(String id) { this.id = id; }
@Override
public boolean equals(Object o) {
return o instanceof Key && ((Key) o).id.equals(this.id);
}
@Override
public int hashCode() { return id.hashCode(); } // 与 equals 保持一致
}
public static void main(String[] args) {
// 只重写 equals 而不重写 hashCode,下面 get 很可能返回 null
var map = new java.util.HashMap<Key, String>();
map.put(new Key("a"), "value");
System.out.println(map.get(new Key("a")));
}
}
常见追问 / 记忆点
- 记忆点:key 的铁律是 equals 与 hashCode 同进退;自定义类做 key 必须两个都重写。
- 追问:为什么用
(n - 1) & hash定位——容量为 2 的幂时它与取模等价且更快。 - 追问:可变 key 是隐蔽 Bug 源:放进 Map 之后不要再修改参与 hashCode 的字段。