IdentityHashMap适用于键需用==而非equals()比较的场景,如Class、Thread等单例对象作key;不适用于String或DTO等依赖值语义的类型;其查找仅基于引用地址和identityHashCode,与HashMap行为本质不同。

IdentityHashMap 适合哪些场景
它只在你需要用 == 而不是 .equals() 来判断键是否相同时才该用。典型例子是缓存类对象的元数据(比如 Class 对象本身作 key)、代理对象映射、或者实现某些 JVM 层面的弱引用/标识绑定逻辑。
常见错误现象:用 String 或自定义对象当 key,发现明明 put 过却 get 不到——因为它们重写了 equals(),而 IdentityHashMap 完全不看这个。
- 适用:key 是
Class、Thread、ClassLoader等单例性强、天然适合引用比较的对象 - 不适用:普通字符串、DTO、Map/Collection 实例等依赖值语义的类型
- 性能影响:哈希计算直接用
System.identityHashCode(),比.hashCode()略快,但扩容逻辑和普通HashMap类似,无本质优势
为什么不能用 HashMap 替代 IdentityHashMap
HashMap 查找时先比 hash,再调用 .equals();而 IdentityHashMap 查找时只比 hash 和引用地址。两者行为差异极大,混用等于埋雷。
例如:
立即学习“Java免费学习笔记(深入)”;
String a = new String("key");
String b = new String("key");
Map<String, Integer> hm = new HashMap<>();
hm.put(a, 1);
System.out.println(hm.get(b)); // 输出 1 —— 因为 "key".equals("key")
Map<String, Integer> im = new IdentityHashMap<>();
im.put(a, 1);
System.out.println(im.get(b)); // 输出 null —— 因为 a != b
这种差异在调试时极难察觉,尤其当 key 看起来“一样”但来源不同(如反射生成、JSON 反序列化、不同 classloader 加载)。
IdentityHashMap 的线程安全性与迭代顺序
它和 HashMap 一样,不保证线程安全,也不保证插入顺序或访问顺序。迭代器是 fail-fast 的,但底层数组结构不支持像 LinkedHashMap 那样稳定遍历。
- 并发写入会抛
ConcurrentModificationException,别指望它能自动同步 - 没有
identityLinkedHashMap这种东西,想保序只能自己封装或换结构 - 如果 key 是临时创建的对象(比如每次 new 出来的包装类),要注意 GC 后无法再通过新实例找回旧值——因为引用地址变了
容易被忽略的构造参数陷阱
IdentityHashMap(int) 的参数不是初始容量,而是“期望容纳的键值对数量”,内部会向上取整到最近的 2 的幂再乘以加载因子(0.75),最终数组长度其实是这个值的约 1.33 倍。这和 HashMap 的构造逻辑一致,但文档没强调这点,容易误判内存占用。
- 传
new IdentityHashMap(16),实际 table 长度可能是 32 - 传 0 或负数会触发默认初始化(table 长度为 32)
- 没有
IdentityHashMap(Map)构造函数,不能直接从其他 Map 初始化,得手动putAll()
真正麻烦的是:一旦用了它,后续所有 key 比较逻辑都锁定在引用层面,连 debug 打印都可能误导人——因为两个内容相同的对象,在日志里看起来一模一样,但 map 就是不认。










