本教程将带你学习Java中IdentityHashMap类的使用,并探讨它与普通HashMap类的区别。虽然IdentityHashMap实现了Map接口,但它违反了Map接口的契约——这可能是你踩坑的起点。更详细的文档可以参考官方JavaDoc,关于HashMap的详细内容可以参考Java HashMap指南。
2. About the IdentityHashMap Class
这个类实现了Map接口,但行为上却是个"叛逆者":
- 违反Map契约:Map接口规定键比较必须使用
equals()
方法,但IdentityHashMap偏不 - 使用引用相等:在键搜索操作中直接使用
==
比较内存地址 - 特殊哈希策略:使用
System.identityHashCode()
而非hashCode()
- 线性探测技术:哈希表搜索采用线性探测法
这些特性让IdentityHashMap在特定场景下性能更优,但使用不当容易踩坑。
3. Using the IdentityHashMap Class
对象构造和方法签名与HashMap相同,但行为因引用平等而不同。
3.1. 创建对象
三种创建方式,简单粗暴:
// 默认构造器
IdentityHashMap<String, String> identityHashMap = new IdentityHashMap<>();
// 指定初始容量
IdentityHashMap<Book, String> identityHashMap = new IdentityHashMap<>(10);
// 从其他Map初始化
IdentityHashMap<String, String> identityHashMap = new IdentityHashMap<>(otherMap);
未指定容量时默认为21
3.2. 增删改查操作
// 添加元素
identityHashMap.put("title", "Harry Potter and the Goblet of Fire");
identityHashMap.put("author", "J. K. Rowling");
identityHashMap.put("language", "English");
identityHashMap.put("genre", "Fantasy");
// 批量添加
identityHashMap.putAll(otherMap);
// 获取值
String value = identityHashMap.get(key);
// 更新值(返回旧值)
String oldTitle = identityHashMap.put("title", "Harry Potter and the Deathly Hallows");
assertEquals("Harry Potter and the Goblet of Fire", oldTitle);
// 删除元素
identityHashMap.remove("title");
3.3. 遍历条目
两种遍历方式,注意fail-fast机制:
// 使用entrySet遍历
Set<Map.Entry<String, String>> entries = identityHashMap.entrySet();
for (Map.Entry<String, String> entry: entries) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 使用keySet遍历
for (String key: identityHashMap.keySet()) {
System.out.println(key + ": " + identityHashMap.get(key));
}
遍历时修改Map会抛出
ConcurrentModificationException
3.4. 其他方法
方法 | 说明 |
---|---|
clear() |
清空所有条目 |
containsKey() |
检查键存在性(引用比较) |
containsValue() |
检查值存在性(引用比较) |
keySet() |
返回基于引用的键集合 |
size() |
返回条目数量 |
values() |
返回值集合 |
3.5. 支持Null键和值
IdentityHashMap<String, String> identityHashMap = new IdentityHashMap<>();
identityHashMap.put(null, "Null Key Accepted");
identityHashMap.put("Null Value Accepted", null);
assertEquals("Null Key Accepted", identityHashMap.get(null));
assertEquals(null, identityHashMap.get("Null Value Accepted"));
3.6. 并发处理
IdentityHashMap不是线程安全的,多线程场景需要同步包装:
Map<String, String> synchronizedMap = Collections.synchronizedMap(new IdentityHashMap<String, String>());
4. 引用相等性示例
IdentityHashMap的核心特性:引用相等优先于值相等
// 创建IdentityHashMap
IdentityHashMap<String, String> identityHashMap = new IdentityHashMap<>();
identityHashMap.put("title", "Harry Potter and the Goblet of Fire");
identityHashMap.put("author", "J. K. Rowling");
identityHashMap.put("language", "English");
identityHashMap.put("genre", "Fantasy");
// 创建普通HashMap
HashMap<String, String> hashMap = new HashMap<>(identityHashMap);
hashMap.put(new String("genre"), "Drama");
assertEquals(4, hashMap.size()); // 值相等,更新现有键
// IdentityHashMap行为不同
identityHashMap.put(new String("genre"), "Drama");
assertEquals(5, identityHashMap.size()); // 引用不同,视为新键
相同内容的不同String对象,在IdentityHashMap中会被视为不同键
5. 可变键支持
IdentityHashMap允许使用可变键,这是它区别于HashMap的实用特性:
class Book {
String title;
int year;
// 其他方法包括equals, hashCode和toString
}
// 创建可变键对象
Book book1 = new Book("A Passage to India", 1924);
Book book2 = new Book("Invisible Man", 1953);
// HashMap中的问题
HashMap<Book, String> hashMap = new HashMap<>(10);
hashMap.put(book1, "A great work of fiction");
hashMap.put(book2, "won the US National Book Award");
book2.year = 1952; // 修改键对象
assertEquals(null, hashMap.get(book2)); // 找不到值
// IdentityHashMap正常工作
IdentityHashMap<Book, String> identityHashMap = new IdentityHashMap<>(10);
identityHashMap.put(book1, "A great work of fiction");
identityHashMap.put(book2, "won the US National Book Award");
book2.year = 1951; // 修改键对象
assertEquals("won the US National Book Award", identityHashMap.get(book2)); // 仍能找到
引用相等性让IdentityHashMap不受键对象修改影响
6. 值比较的坑
Java 20前IdentityHashMap有个隐藏陷阱:键用引用比较,值却用值比较
Book book = new Book("A Passage to India", 1924);
IdentityHashMap<Book, String> identityHashMap = new IdentityHashMap<>(10);
identityHashMap.put(book, "A great work of fiction");
// Java 20前:remove使用值比较
identityHashMap.remove(book, new String("A great work of fiction")); // 会删除!
assertEquals(null, identityHashMap.get(book));
// replace同样问题
identityHashMap.replace(book, new String("A great work of fiction"), "One of the greatest books");
assertEquals("One of the greatest books", identityHashMap.get(book));
Java 20修复了这个问题,升级时要注意行为变化
Java 20+的正确行为:
// remove现在使用引用比较
identityHashMap.remove(book, new String("A great work of fiction"));
assertEquals("A great work of fiction", identityHashMap.get(book)); // 不会被删除
// replace同样使用引用比较
identityHashMap.replace(book, new String("A great work of fiction"), "One of the greatest books");
assertEquals("A great work of fiction", identityHashMap.get(book)); // 不会被替换
7. 典型使用场景
IdentityHashMap不是通用Map,适合特定场景:
- 代理对象管理:维护一组可变对象的代理
- 引用缓存:基于对象引用的快速缓存
- 对象引用图:内存中维护带引用的对象图
8. 总结
我们深入探讨了IdentityHashMap的使用方式、与HashMap的核心差异,以及它的特殊应用场景。完整代码示例可在GitHub获取。记住:这个工具很强大,但用错地方就是个大坑。