bigywater.tech
Java · 172

IdentityHashMap:为什么它使用 == 而不是 equals

IdentityHashMap 为什么用 == 而不是 equals?内部长什么样?什么时候该用?这篇一项项量出来(HashMap 的树化、扩容在已有文章里)。

2 vs 1
两个内容相同的 String 键,IdentityHashMap 与 HashMap 的 size
键值相邻
table 里键在偶数下标、值在下一个槽
2.8 倍
键的 hashCode 很贵时 IdentityHashMap 相对 HashMap 的速度

身份比较

实测 · 本机执行输出
  两个内容相同但不是同一个对象的 String(k1.equals(k2) = true,k1 == k2 = false):HashMap.size() = 1,IdentityHashMap.size() = 2
  id.get(k1) = 1,id.get(k2) = 2,id.get(new String("key")) = null(换一个新对象就找不到)
  两个字面量 "key" 是同一个对象(intern):IdentityHashMap.size() = 1
  装箱的 Integer:put(100) 两次 + put(1000) 两次,size = 3(100 在 Integer 缓存里是同一个对象,1000 每次装箱都是新对象)
  null 键:id.put(null, 9) 可以,id.get(null) = 9

内部结构

实测 · 本机执行输出
  元素个数增长时 table 数组长度的变化: 第 1 个元素时 table.length=64; 第 22 个元素时 table.length=128; 第 43 个元素时 table.length=256; 第 86 个元素时 table.length=512;
  table 是一个 Object[]:键和值交替放在相邻的槽里(长度 = 2 × 容量),没有 Node 对象;100 个元素时 table.length = 512
  只放一个键 "K" → 12345:键在 table[48](偶数下标),table[49] = 12345(紧挨着的下一个槽就是值);table 里非空元素的类型 = [Integer, String](只有键和值本身,没有 Entry/Node 对象)
  new IdentityHashMap(100) 的 table.length = 512

耗时

实测 · 本机执行输出
  键是含 100 个元素的 List(hashCode 与 equals 都要遍历 100 个元素):200000 次 put + 200000 次 get:HashMap 91 ms,IdentityHashMap 32 ms(2.8 倍)

按身份去重

实测 · 本机执行输出
  图里有两个 name 相同(equals 为 true)但不是同一个的节点 a1、a2,以及 b:按身份记录访问过的节点集合大小 = 3,而用 HashSet(按 equals)是 2(a1 和 a2 被当成同一个)

分步演示

IdentityHashMap
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12(读 table 用了 --add-opens java.base/java.util=ALL-UNNAMED)
    • 没有验证:内存占用对比、线性探测的冲突率、iterator 与 entrySet 的细节、JDK 17 与 27 的实现差异、在 equals 很便宜的小键上的耗时(那种情况下两者可能没有差别);耗时是一次运行的结果
    京ICP备2026067502号-1京公网安备11010502063487号