IdentityHashMap 使用 System.identityHashCode() 而非对象的 hashCode() 计算哈希值,通过扰动函数处理后与数组长度减一按位与得索引,并采用线性探测法解决冲突,确保基于引用相等(==)的映射语义。

IdentityHashMap 的哈希值计算不调用对象的 hashCode() 方法,而是直接使用 System.identityHashCode() 的返回值作为哈希依据。
哈希值来源就是 System.identityHashCode
IdentityHashMap 内部在计算桶索引时,会调用 System.identityHashCode(key)(对 key)或 System.identityHashCode(value)(对 value),然后对结果做无符号右移和异或混合(类似 HashMap 的扰动函数),再与数组长度减一做按位与得到下标。
这个 identityHashCode 由 JVM 保证:只要对象没被重定义(即未被 finalize 或 GC 回收),其 identityHashCode 在整个生命周期内保持不变;它与对象内存地址强相关(但不一定是地址本身,JVM 可优化),且与 Object.hashCode() 无关——即使你重写了 hashCode(),IdentityHashMap 也完全忽略它。
实际计算过程简析
以 JDK 21 的 IdentityHashMap 源码逻辑为例(简化后):
立即学习“Java免费学习笔记(深入)”;
- 先获取原始哈希码:
int h = System.identityHashCode(key); - 为减少低位冲突,做一次简单扰动:
h ^= h >>> 2;(不同 JDK 版本扰动方式略有差异,但目的相同) - 根据哈希表当前容量
tab.length(一定是 2 的幂),计算索引:int i = h & (tab.length - 1);
注意:IdentityHashMap 不使用链表或红黑树处理哈希冲突,而是采用**线性探测法**(open addressing),所以实际存放位置可能因冲突而偏移到后续槽位。
为什么不用 Object.hashCode()?
因为 IdentityHashMap 的语义是“基于引用相等(==)的映射”,它的设计目标是区分两个内容相同但不同实例的对象。例如:
String a = new String("hello");
String b = new String("hello");
System.out.println(a.equals(b)); // true
System.out.println(a == b); // false
System.out.println(a.hashCode() == b.hashCode()); // true(内容相同)
System.out.println(System.identityHashCode(a) == System.identityHashCode(b)); // 几乎一定 false
如果用 hashCode(),a 和 b 会映射到同一桶,违背“引用唯一性”原则;而用 identityHashCode(),它们大概率分散开,配合 == 判断,才能真正实现“按引用存储和查找”。
注意事项
- identityHashCode 是 JVM 级别生成的,不可预测也不可修改
- 对于 null 键或值,IdentityHashMap 统一用 0 作为其 identityHashCode(因为
System.identityHashCode(null)返回 0) - 由于使用线性探测,负载因子过高时性能下降明显,建议控制 size / capacity ≤ 0.75
- 该类不是线程安全的,多线程需额外同步


















