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 在桶内“漂移”成脏数据
允许 nullkey 为 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 的字段。
笔记加载中…