identityhashmap用==比较key地址而非equals(),故相同内容不同对象视为独立key;其内部用object[]线性存储key-value对,查找为步长2的线性探测,适用于需对象身份唯一性的场景。

IdentityHashMap 为什么能存“相同内容但不同对象”的 key
因为 IdentityHashMap 不调用 equals(),只用 == 判断 key 是否相等——也就是比内存地址。两个 new String("a") 内容一样,但地址不同,它就当它们是两个独立 key。
- 常见错误现象:用
HashMap存多个内容相同的临时对象,结果值被意外覆盖;换成IdentityHashMap后“重复 key”突然不覆盖了,误以为是 bug - 典型使用场景:需要区分对象身份(identity)而非值(value),比如缓存对象生命周期钩子、代理对象去重、JVM 关闭钩子注册表(源码里真这么用)
- 注意:字符串字面量(如
"test")在常量池中复用地址,所以IdentityHashMap.put("a", 1); put("a", 2)仍会覆盖——这不是 bug,是 JVM 特性
和 HashMap 的底层行为差异在哪
IdentityHashMap 表面像 HashMap,实际存储结构完全不同:它不用 Node 或链表/红黑树,而是把 key 和 value 挨着塞进一个 Object[] table,table[i] 是 key,table[i+1] 是 value。
- 查找逻辑:先 hash 定位起始下标,再从那里开始以步长 2 往后线性探测(
i,i+2,i+4…),直到找到==的 key 或空位 - 性能影响:没有链表/树优化,冲突多时退化成接近 O(n) 查找;默认初始容量是 32(
HashMap是 16),扩容也更激进 - 参数无区别:构造函数签名和
HashMap一致(支持 initialCapacity / loadFactor),但 loadFactor 实际作用有限——它不控制链表转树,只影响数组扩容阈值
什么时候该用 IdentityHashMap,而不是自己重写 equals/hashCode
当你明确需要“对象实例唯一性”,且无法或不应修改目标类的 equals() 和 hashCode() 行为时,IdentityHashMap 是最轻量的解法。
- 适用条件:key 类型是第三方类 / final 类 / 不可修改源码的类,或者你故意要绕过业务语义的相等判断(比如调试时追踪对象创建路径)
- 反模式:用它替代
HashMap来“解决 equals 写错的问题”——这掩盖设计缺陷,且后续维护者极易误解语义 - 兼容性注意:它允许
null作为 key 和 value(HashMap也允许 null key,但IdentityHashMap对 null 的处理更直接:用特殊哨兵标记,不依赖equals(null))
容易被忽略的坑:自动装箱让基本类型表现反直觉
对 Integer、Boolean 等包装类,JVM 有缓存机制(-128~127 的 Integer 默认复用对象),所以 IdentityHashMap.put(100, "a"); put(100, "b") 可能覆盖,而 put(200, "a"); put(200, "b") 却不覆盖。
- 根本原因:前者两次
100装箱得到同一个对象引用;后者得到两个不同地址的对象 - 实操建议:如果 key 是数值且需严格按实例区分,优先用原始类型变量名 + 注释说明,或显式用
new Integer(x)(虽不推荐,但能暴露意图) - 调试技巧:打印 key 的
System.identityHashCode(),比看toString()更可靠










