identityhashmap 采用线性探测的开放寻址法,用单个object[]数组交替存储key-value,以==判等、忽略hashcode和equals;空槽用私有哨兵null_key标记,扩容时全量重散列填充。

IdentityHashMap 不是用“数组代替哈希桶”,而是根本不使用哈希桶(即不基于 hashCode 拉链/红黑树结构),它用一个线性探测的开放寻址法数组直接存储键值对——这个数组既是“底层数组”,也承担了“哈希表”的全部职责,没有传统 HashMap 中的 Node[] 桶数组 + 链表/树结构。
它的设计目标很明确:**只按 == 判断相等,完全忽略 hashCode 和 equals,且追求极致简单和速度**。因此它绕开了哈希冲突的复杂处理(如拉链、扩容重散列、树化),改用更轻量的开放寻址策略。
核心机制:一维数组 + 线性探测
IdentityHashMap 底层只有一个 Object[] table 数组(长度恒为 2 的幂),每个索引位置连续存放 key 和 value(即 table[i] 是 key,table[i+1] 是对应 value)。空位用 NULL_KEY(一个私有静态哨兵对象)标记,而非 null(因为 null 是合法键)。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 插入时,先用
System.identityHashCode(key) & (table.length - 1)计算初始下标 - 若该位置已被占用(key 不为 NULL_KEY),则顺序向后查找下一个空位(i+2, i+4…),直到找到
NULL_KEY或遇到第一个null(表示已探查到未使用区域末尾) - 查 key 时,同样从初始下标开始线性探测,逐个比对
==,直到匹配或遇到NULL_KEY/null
为什么不用“桶”?因为不需要分离链地址
传统 HashMap 的 Node[] 是“桶数组”,每个桶指向一个链表或树;而 IdentityHashMap 的 Object[] 是“数据数组”,key-value 成对紧邻存放,逻辑上每个“槽位”只容纳一对映射。冲突不通过链表解决,而是挪到下一个可用槽位——这正是开放寻址法的本质。
- 没有 Node 对象,避免额外内存开销和 GC 压力
- 没有 equals 调用,也不依赖 key 的 hashCode 实现(只用 identityHashCode)
- 遍历时按数组顺序访问,局部性好,CPU 缓存友好
扩容与 rehash:数组重建,非桶迁移
当负载因子(实际键数 / 数组长度)超过阈值(≈ 0.75),就创建一个两倍长的新 Object[],然后把旧数组中所有非空 key-value 对**重新计算位置并线性插入新数组**——这不是“把旧桶里的链表复制过去”,而是对每对数据独立执行一次完整的插入逻辑。
- 旧数组中 key-value 是交错存放的(k0,v0,k1,v1,…),扫描时每次取两个元素
- 新数组插入仍走线性探测,可能位置和原来完全不同
- 没有“桶迁移”概念,只有“数据重散列 + 顺序填充”
注意:NULL_KEY 是关键设计细节
由于 null 可以作为合法键(map.put(null, "x") 允许),IdentityHashMap 不能用 null 标记空槽。它定义了一个私有静态对象 NULL_KEY = new Object(),所有空 key 槽都存这个对象,value 槽对应存 null。这样既区分了“空槽”和“键为 null”,又不影响 == 判断语义。
- put(null, v) → 存
NULL_KEY和v - get(null) → 查找时用
null == key匹配NULL_KEY - 这是纯实现技巧,对外透明,但支撑了整个开放寻址逻辑
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










