identityhashmap 用 == 而非 equals() 判断 key 相等,故相同内容不同实例的 key 被视为不同;允许多个 null key(不同引用),扩容开销大且不支持序列化后保持引用语义。

IdentityHashMap 为什么不用 equals() 判断 key 相等
它用 == 比较 key,不是 equals()。这意味着两个内容相同但不同实例的 String,在 IdentityHashMap 里算两个不同的 key;而同一个对象无论被 put 多少次,都只占一个槽位。
常见错误现象:map.get(new String("a")) 返回 null,哪怕你之前 put(new String("a"), 1) 过——因为两次 new String("a") 是不同对象,== 不成立。
- 适用场景:缓存对象元数据(比如给某个特定
Object实例挂一个临时配置)、实现对象图遍历时标记已访问节点、避免因重写equals()引发的意外覆盖 - 不适用场景:按业务语义查数据(比如“用户名为 admin 的用户”),这种必须用
HashMap - 注意:value 仍用
equals()和hashCode(),只有 key 的相等逻辑被替换
IdentityHashMap 和 HashMap 在 null key 上的行为差异
IdentityHashMap 允许任意数量的 null key,只要它们是不同引用;而 HashMap 把所有 null 当作同一个 key,只保留最后一次 put 的值。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 如果你要区分 “这个
null是第一次传入的空引用” 和 “那个null是另一个地方传来的空引用”,IdentityHashMap可以做到;HashMap不行 - 但要注意:多个
nullkey 会导致size()增长,迭代时也会看到多个null条目,容易误判为数据异常 - 示例:
map.put(null, "first"); map.put(null, "second");后,IdentityHashMap的size()是 2,HashMap是 1
IdentityHashMap 的扩容机制和内存开销
它内部用线性探测法处理哈希冲突,数组长度总是 2 的幂,扩容时直接复制整个数组并重新散列——不像 HashMap 那样能复用部分桶结构。
性能影响明显:
- 初始容量设太小(如默认 16)+ 频繁 put 不同对象 → 触发多次扩容 → 明显 GC 压力
- key 数量稳定且可预估时,务必在构造时指定足够大的初始容量,比如
new IdentityHashMap(1024) - 不要把它当通用 Map 用:没有红黑树优化,大量哈希碰撞时查找退化为 O(n),比
HashMap更敏感
序列化 IdentityHashMap 会丢失引用语义
序列化后反序列化出来的 IdentityHashMap,key 的 == 关系无法恢复——因为新对象和原对象内存地址不同。
这意味着:
- 不能靠序列化保存 “某对象实例到配置”的映射关系,反序列化后
get(obj)必然失败 - 如果必须持久化,得自己维护对象 ID(比如
System.identityHashCode()+ 弱引用缓存),或改用基于业务 key 的方案 - 单元测试里 mock 对象做 key 时,别依赖序列化断言,容易在 CI 环境里偶然失败
引用相等这件事,只在单个 JVM 生命周期内可靠。跨进程、跨序列化、跨 classloader,它就失效了。










