IdentityHashMap使用==比较键,依赖内存地址而非equals(),适用于需引用相等性的场景如对象元数据缓存;不适用常规业务键值存储。

IdentityHashMap用的是==而不是equals
它判断两个键是否相等,靠的是内存地址是否相同(==),不是调用equals()方法。这意味着哪怕两个对象内容完全一样、equals()返回true,只要不是同一个实例,IdentityHashMap就认为它们是不同的键。
常见错误现象:
你把一个自定义对象放入IdentityHashMap,又新建了一个字段值一模一样的对象去get,结果返回null——不是bug,是设计如此。
- 适用场景:缓存对象元数据、代理/装饰器链中需要严格区分实例、序列化中间状态跟踪
- 不适用场景:常规业务键值存储(比如用户ID→用户信息)、任何依赖逻辑相等性的场合
- 注意:
IdentityHashMap的containsKey()、get()、remove()全走==,连keySet()迭代出来的也是原始引用
HashMap的key必须重写hashCode和equals
因为HashMap先用hashCode()定位桶,再用equals()确认是否真匹配。如果只重写equals()不重写hashCode(),或者两者逻辑不一致,就会出现“能put进去但get不出来”的问题。
典型错误:new Person("Alice", 25)和另一个new Person("Alice", 25)在HashMap里被当成不同key——大概率是因为没重写hashCode(),或hashCode()没包含所有equals()用到的字段。
立即学习“Java免费学习笔记(深入)”;
- 必须同时重写
hashCode()和equals(),且保证:x.equals(y)为true → x.hashCode() == y.hashCode() - 字段变更后,若该对象已作为
HashMap的key,不要再修改影响hashCode()或equals()的字段,否则可能再也找不到它 - 使用IDE生成的
hashCode()/equals()一般安全;手写时务必检查字段覆盖完整性
IdentityHashMap没有哈希冲突问题?错,它有,但解决方式不同
它内部也用数组+链表(Java 8+是链表或红黑树),但计算索引的方式是System.identityHashCode(key) & (table.length - 1),这个identityHashCode跟对象内存地址强相关,且不可被重写。
所以它的“冲突”不是因为hashCode()碰撞,而是因为不同对象的System.identityHashCode()可能相同(虽然概率低),或者扩容后重散列导致位置变化。
- 性能上:避免了
equals()调用开销,对大量短生命周期临时对象做键时略快 - 但扩容成本更高——每次rehash都要重新算所有key的
System.identityHashCode(),且无法复用已有hashCode()缓存 - 兼容性无特殊问题,但别指望它替代
HashMap来“优化性能”,除非你明确需要引用语义
什么时候该选IdentityHashMap而不是WeakHashMap或ConcurrentHashMap
三者解决的问题完全不同:WeakHashMap关注GC生命周期,ConcurrentHashMap关注线程安全,IdentityHashMap只关注引用相等性。混用会出大问题。
容易踩的坑:
误以为IdentityHashMap是线程安全的——它不是,多线程读写要自己同步;
误把它当弱引用容器——它持有强引用,不会因为GC丢失key。
- 选
IdentityHashMap唯一正当理由:你真的需要==语义,且能控制key的创建方式(比如只用单例、静态实例或池化对象) - 如果key可能被GC回收,用
WeakHashMap;如果要并发读写,用ConcurrentHashMap;如果只是想省掉重写hashCode(),那说明设计有问题 - 调试时注意:打印
IdentityHashMap内容看不出区别,得用System.identityHashCode(obj)手动比对
最常被忽略的一点:IdentityHashMap的迭代顺序不保证与插入顺序一致,也不按任何哈希规律排列——它本质是基于地址哈希的开放寻址变体,遍历时按内部数组下标顺序扫,而数组下标由System.identityHashCode()决定,这个值在JVM重启间不保证稳定。









