IdentityHashMap 是什么?它和 HashMap 有什么区别?
简化版
IdentityHashMap 是一个特殊的 Map——它判断两个 key 是否「相同」时用的是 ==(引用相等,即是不是同一个对象),而不是 HashMap 用的 equals()(值相等)。也就是说:普通 HashMap 里两个「内容相等」的 key 会被当成同一个 key(覆盖),而 IdentityHashMap 里只有「是同一个对象(同一内存地址)」才算同一个 key。它的哈希也用 System.identityHashCode()(基于对象地址)而非 key 的 hashCode()。用途很小众但明确:需要**「按对象身份而非内容」区分 key** 的场景,如序列化时记录「已处理过的对象」(防循环引用)、对象图遍历(拓扑排序)、为每个对象实例附加信息。它不是给日常用的通用 Map。
详细版
核心区别:== vs equals:
String a = new String("key");
String b = new String("key"); // a 和 b 内容相同,但是两个不同的对象(a != b)
// HashMap:用 equals 判断 key —— a 和 b 被当成同一个 key
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put(a, 1);
hashMap.put(b, 2); // b.equals(a) 为 true → 覆盖!
hashMap.size(); // 1(a 和 b 是同一个 key)
// IdentityHashMap:用 == 判断 key —— a 和 b 是不同对象、不同 key
Map<String, Integer> idMap = new IdentityHashMap<>();
idMap.put(a, 1);
idMap.put(b, 2); // a != b → 是两个不同的 key
idMap.size(); // 2!(a 和 b 被当成两个 key)
IdentityHashMap vs HashMap:
| 维度 | HashMap | IdentityHashMap |
|---|---|---|
| key 相等判断 | key1.equals(key2) | key1 == key2(引用相等) |
| 哈希计算 | key.hashCode() | System.identityHashCode(key)(基于对象地址) |
| 相等语义 | 内容相等 | 对象身份相等(同一个对象) |
| 底层结构 | 数组 + 链表/红黑树 | 数组(线性探测法解决冲突) |
| 用途 | 通用键值存储 | 按对象身份区分的特殊场景 |
⚠️
IdentityHashMap故意违反了 Map 接口的常规约定(Map 约定用 equals 判等)——它的 JavaDoc 明确说「这个类不是通用 Map 实现,故意用 == 而非 equals」。所以别把它当普通 Map 用——如果你用它存 String/Integer 等,可能出现「内容相同但被当成不同 key」的诡异行为(尤其 String 常量池的字符串 == 为 true、new 出来的 == 为 false,行为不一致)。它只在明确需要「引用相等语义」时才用。
完整版教学
一、核心区别:判等用 == 而非 equals
IdentityHashMap 和 HashMap 的唯一本质区别是——判断两个 key 是否「相同」用什么:
HashMap:用 equals() 判断 key 相等(值相等/内容相等)
两个 key 只要 equals 返回 true,就是"同一个 key"(后者覆盖前者)
→ 符合直觉:内容一样的 key 视为同一个
IdentityHashMap:用 == 判断 key 相等(引用相等/身份相等)
两个 key 只有是"同一个对象"(同一内存地址,== 为 true)才是"同一个 key"
→ 内容一样但是不同对象 → 视为两个不同的 key
关键认知:IdentityHashMap 关心的是「对象身份(是不是同一个对象)」而非「对象内容(值是否相等)」。这带来的直接效果是——两个「内容完全相同但是不同对象」的 key,在 HashMap 里是一个 key(覆盖),在 IdentityHashMap 里是两个 key(共存)。理解「HashMap 用 equals 判内容相等、IdentityHashMap 用 == 判身份相等」,就抓住了两者的本质区别——一个看内容、一个看身份。
二、哈希也不同:System.identityHashCode
除了判等用 ==,IdentityHashMap 的哈希计算也不同——用 System.identityHashCode() 而非 key 的 hashCode():
// HashMap:用 key.hashCode()(可能被重写,基于内容)
"key".hashCode(); // 基于字符串内容的哈希
// IdentityHashMap:用 System.identityHashCode()(基于对象地址,不受 hashCode 重写影响)
System.identityHashCode(obj); // 对象的"原始哈希码"(近似基于地址),即使 obj 重写了 hashCode
为什么要用 identityHashCode?因为IdentityHashMap 要保证「== 相等的对象有相同的哈希、!= 的对象哈希不同」——如果用 key.hashCode()(可能被重写成基于内容),那两个「内容相同但不同对象」的 key 会有相同的 hashCode,就无法正确区分它们了。System.identityHashCode() 返回对象的「原始身份哈希」(不受 hashCode 重写影响、近似基于对象),保证了「同一对象哈希相同、不同对象哈希(大概率)不同」,和 == 判等语义一致。理解「IdentityHashMap 用 identityHashCode 保证哈希和 == 判等语义一致」,就理解了它为什么连哈希都要特殊处理——为了和「身份相等」的语义匹配。
三、底层结构:线性探测
IdentityHashMap 的底层实现也和 HashMap 不同——它用线性探测法(开放寻址) 而非「数组 + 链表」:
HashMap:数组 + 链表/红黑树(拉链法解决冲突)
冲突的 key 挂在同一个桶的链表上
IdentityHashMap:一个数组,key 和 value 交替存放,线性探测解决冲突
数组:[key0, value0, key1, value1, ...]
冲突时(哈希位置已占)→ 往后线性找下一个空位(开放寻址)
为什么用线性探测?因为 IdentityHashMap 的场景(对象身份、通常 key 数量不多)下,开放寻址(数组连续、缓存友好)比拉链法更高效,且实现简单。它把 key 和 value 交替放在一个 Object[] 数组里(table[i]=key、table[i+1]=value)。这是个实现细节,但能体现「IdentityHashMap 是为特定场景优化的、不是通用 Map」。理解「IdentityHashMap 用线性探测(开放寻址)、key/value 交替存数组」,就了解了它和 HashMap 在底层结构上的不同——不过面试更关注的是「== vs equals」这个语义区别。
四、经典用途一:防循环引用
IdentityHashMap 最经典的用途是「记录『已经处理过的对象』,防止循环引用导致死循环」——序列化、深拷贝、对象图遍历时常用:
// 深拷贝/序列化时,记录已访问的对象,防止循环引用无限递归
IdentityHashMap<Object, Object> visited = new IdentityHashMap<>();
Object deepCopy(Object obj) {
if (visited.containsKey(obj)) { // 用 == 判断"这个对象是否已处理过"
return visited.get(obj); // 处理过了,直接返回(防止循环)
}
Object copy = createCopy(obj);
visited.put(obj, copy); // 记录"这个对象已处理"
// 递归处理 obj 的字段...
return copy;
}
为什么这里必须用 IdentityHashMap 而非 HashMap?因为要判断的是「这个『对象』是否处理过」(对象身份),而不是「这个『内容』是否处理过」——两个内容相同但不同的对象,是要分别处理的(它们是对象图里不同的节点)。如果用 HashMap(equals 判等),两个内容相同的不同对象会被当成「同一个」,导致漏处理或错误。序列化框架(如 JDK 序列化)内部就用类似机制记录「已序列化的对象」,防止循环引用(A 引用 B、B 引用 A)时无限递归。理解「IdentityHashMap 用于按对象身份记录『已处理对象』、防循环引用」,就掌握了它最重要的应用场景。
五、经典用途二、三:对象图与元数据
另外两个用途:
用途二:对象图遍历(拓扑排序、图算法)
遍历对象图(对象互相引用形成的图)时,需要标记"访问过的节点"
节点是"对象",要按对象身份区分(不同对象是不同节点,哪怕内容相同)
→ 用 IdentityHashMap 记录访问状态
用途三:为每个对象实例附加信息(按实例区分)
想给"每个具体的对象实例"关联一份数据(不是按内容,是按实例)
→ 用 IdentityHashMap<对象, 附加信息>
→ 两个内容相同的不同实例,各自有各自的附加信息
这两个用途的共同点是「需要按『对象实例』而非『内容』来区分和标记」。对象图遍历里,图的每个节点是一个具体对象(不同对象=不同节点),要用身份区分;给对象附加信息时,想让「每个实例」有自己的信息(哪怕内容相同的两个实例也要分开),也要用身份。这些场景下用 HashMap(equals 判等)会把「内容相同的不同对象」混为一谈,出错。理解「IdentityHashMap 用于对象图遍历标记、按实例附加信息」,就补齐了它的应用图景——都是「按对象身份区分」的需求。
六、什么时候用、什么时候别用
IdentityHashMap 用途小众,选择很明确:
用 IdentityHashMap(需要"对象身份"语义):
✓ 序列化/深拷贝时记录已处理对象(防循环引用)
✓ 对象图遍历标记访问状态
✓ 按对象实例(而非内容)附加信息
→ 核心:需要"区分内容相同但不是同一个对象的 key"
别用 IdentityHashMap(当普通 Map 用会出错):
✗ 存 String/Integer 等(== 行为不确定:常量池的 == 为 true、new 的为 false)
✗ 需要"内容相等"语义的场景(那是 HashMap 的活)
✗ 日常键值存储(用 HashMap)
核心判断:需要「按对象身份(是不是同一个对象)区分 key」→ IdentityHashMap;需要「按内容相等区分 key」→ HashMap。IdentityHashMap 是「特殊工具」不是「通用容器」——它的 JavaDoc 都明说「不是通用 Map 实现」。日常几乎用不到它,但理解它的存在和「== vs equals」的区别,能加深对「对象相等性」的理解(== 比地址、equals 比内容——这也是 equals 和 == 那道高频题的延伸)。理解「按身份用 IdentityHashMap、按内容用 HashMap、它是特殊工具非通用」,就知道了它的定位——冷门但在特定场景不可替代。
记忆钩子:「IdentityHashMap 用 ==(引用/身份相等,是不是同一个对象)判 key,而 HashMap 用 equals(内容相等);哈希用 System.identityHashCode(基于地址);两个内容相同但不同的对象,HashMap 视为一个 key(覆盖)、IdentityHashMap 视为两个 key;用途:序列化/深拷贝防循环引用(记录已处理对象)、对象图遍历标记、按实例附加信息;它是特殊工具非通用 Map,别拿来存 String/Integer」。
七、常见误区与追问
- 误区:IdentityHashMap 和 HashMap 只是名字不同。 本质区别是判 key 相等的方式——IdentityHashMap 用
==(引用/身份相等),HashMap 用equals()(内容相等);哈希也不同(identityHashCode vs hashCode)。 - 误区:IdentityHashMap 是更快的 HashMap。 不是——它是「按对象身份区分 key」的特殊 Map,用途小众(防循环引用等),不是通用 Map;日常键值存储用 HashMap。
- 误区:可以用 IdentityHashMap 存字符串。 危险——String 的 == 行为不确定(常量池的相同字符串 == 为 true、new 出来的为 false),会出现内容相同却被当成不同/相同 key 的诡异行为;别拿它存 String/Integer。
- 误区:IdentityHashMap 也用链表解决冲突。 它用线性探测(开放寻址),key 和 value 交替存在一个数组里,不是 HashMap 的数组+链表结构。
- 追问:IdentityHashMap 和 HashMap 的区别? IdentityHashMap 用
==判 key 相等(对象身份/是不是同一个对象)、用 System.identityHashCode 计算哈希;HashMap 用 equals 判相等、用 key.hashCode 计算哈希。两个内容相同但不同的对象,IdentityHashMap 视为两个 key、HashMap 视为一个。 - 追问:IdentityHashMap 有什么用途? 需要「按对象身份区分」的场景——序列化/深拷贝时记录已处理对象防循环引用、对象图遍历标记访问状态、按对象实例(而非内容)附加信息。
- 追问:为什么 IdentityHashMap 要用 identityHashCode 而非 hashCode? 为保证哈希和 == 判等语义一致——如果用可能被重写成基于内容的 hashCode,两个内容相同的不同对象哈希会相同、无法区分;identityHashCode 基于对象身份,保证同一对象哈希相同、不同对象哈希不同。
八、加强记忆
IdentityHashMap 的核心是「判 key 相等用 ==(引用/身份相等,是不是同一个对象)而非 equals(内容相等)」——这是它和 HashMap 的本质区别。效果:两个「内容相同但是不同对象」的 key,HashMap 视为同一个 key(equals 相等、覆盖)、IdentityHashMap 视为两个不同 key(== 不等、共存)。它的哈希也用 System.identityHashCode()(基于对象身份/地址) 而非 key 的 hashCode(保证哈希和 == 判等语义一致),底层用线性探测(开放寻址)、key/value 交替存数组。它故意违反 Map 用 equals 判等的约定、不是通用 Map,用途小众但明确——都是「需要按对象身份而非内容区分 key」的场景:① 序列化/深拷贝时记录『已处理对象』防循环引用(最经典)、② 对象图遍历标记访问状态、③ 按对象实例(非内容)附加信息。别拿它当普通 Map(存 String/Integer 会因 == 行为不确定而出诡异问题)。日常几乎用不到,但它加深了对「== 比身份、equals 比内容」的理解。一句话「IdentityHashMap 用 == 判身份相等(HashMap 用 equals 判内容)、内容同但不同对象视为两个 key、用 identityHashCode 哈希、用于序列化防循环引用/对象图遍历,是特殊工具别当通用 Map」。