← 返回题目列表

IdentityHashMap 是什么?它和 HashMap 有什么区别?

中等 第 30 / 30 题 更新于 2026/07/27
IdentityHashMap引用相等==equals

简化版

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

维度HashMapIdentityHashMap
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

IdentityHashMapHashMap 的唯一本质区别是——判断两个 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」。