IdentityHashMap 使用 == 而非 equals() 比较 key,仅依据对象内存地址判等,故相同内容的不同 String 实例被视为不同 key;允许 null 作为唯一 key,基于 System.identityHashCode() 散列,采用线性探测开放寻址,不保证迭代顺序,适用于对象身份映射等严格引用相等场景。

IdentityHashMap 为什么不用 equals() 而用 == 比较 key
它不走 equals() 和 hashCode(),只看两个对象是不是同一个内存地址(==)。这意味着哪怕两个 String 内容完全一样、Objects.equals(a, b) 返回 true,只要不是同一个实例,在 IdentityHashMap 里就算不同 key。
- 典型场景:缓存对象的“身份快照”,比如在序列化/反序列化过程中临时记录对象引用关系,避免循环引用死循环
- 常见错误现象:
map.get(new String("key"))返回null,即使你之前 put 过new String("key")—— 因为是两个不同对象 - 性能影响:跳过
hashCode()计算和equals()调用,插入/查找略快,但牺牲了语义一致性
IdentityHashMap 和 HashMap 在 null key 上的行为差异
IdentityHashMap 允许 null 作为 key,且多个 null 视为同一个 key;而 HashMap 也允许 null key,但它是靠 == 判定 null 的,所以行为表面一致,底层逻辑完全不同。
-
IdentityHashMap把null当作一个特殊地址值处理,所有null引用都指向同一槽位 - 但如果你往里面 put 两次
nullkey,第二次会覆盖第一次——这不是 bug,是设计如此 - 注意:
IdentityHashMap的containsKey(null)和get(null)是安全的,不会 NPE
什么时候该选 IdentityHashMap 而不是 WeakHashMap 或 ConcurrentHashMap
它既不自动清理(不像 WeakHashMap),也不保证线程安全(不像 ConcurrentHashMap),唯一不可替代的点就是“严格按引用判等”。
- 适用场景:对象图遍历、AST 节点映射、代理对象与原对象绑定、JVM 工具类中做 identity-based memoization
- 不适用场景:业务层通用缓存、用户输入字符串做 key、需要跨 JVM 进程共享的映射
- 容易踩的坑:
IdentityHashMap不是线程安全的,多线程并发 put 可能导致数据丢失或无限循环,别试图加锁绕过这点——该换ConcurrentHashMap就换
IdentityHashMap 的迭代顺序和扩容机制很特别
它的内部数组不是按哈希桶链表组织的,而是用线性探测法实现的开放寻址表,所以迭代顺序取决于插入顺序 + 探测路径,不是随机但也不稳定。
- 扩容时会重新散列所有 entry,但因为只依赖
==,不调用hashCode(),所以散列函数是基于System.identityHashCode()的(注意:这个值不保证跨 JVM 一致) - 如果 key 是刚 new 出来的对象,
System.identityHashCode()可能还没被计算过,首次访问才生成,但IdentityHashMap内部会妥善处理 - 别依赖它的迭代顺序做逻辑判断,哪怕测试时看起来“总是按插入顺序”——那是巧合,不是契约
事情说清了就结束。真正要用 IdentityHashMap 的地方不多,一旦用错,bug 往往藏得深:表现像缓存失效,查半天才发现是对象新建导致引用不匹配。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











