Java IdentityHashMap类及其用例

2023/06/07

1. 概述

在本教程中,我们将学习如何在Java中使用IdentityHashMap类。我们还将研究它与一般的HashMap类有何不同。虽然此类实现了Map接口,但它违反了Map接口的约定

有关更详细的文档,我们可以参考IdentityHashMap的Javadoc页面。有关HashMap类的更多详细信息,我们可以阅读Java HashMap指南

2. 关于IdentityHashMap类

该类实现了Map接口。Map接口要求在键比较时使用equals()方法。但是,IdentityHashMap类违反了该约定。相反,它在键搜索操作上使用引用相等性(==)

在搜索操作期间,HashMap使用hashCode()方法进行哈希,而IdentityHashMap使用System.identityHashCode()方法。它还使用哈希表的线性探测技术进行搜索操作。

引用相等性、System.identityHashCode()和线性探测技术的使用使IdentityHashMap类具有更好的性能。

3. 使用IdentityHashMap类

对象构造和方法签名与HashMap相同,但由于引用相等而导致行为不同。

3.1 创建IdentityHashMap对象

我们可以使用默认构造函数创建它:

IdentityHashMap<String, String> identityHashMap = new IdentityHashMap<>();

或者可以使用初始预期容量创建它:

IdentityHashMap<Book, String> identityHashMap = new IdentityHashMap<>(10);

如果我们没有像上面那样指定初始expectedCapacity参数,它将使用21作为默认容量。

我们也可以使用另一个Map对象来创建它:

IdentityHashMap<String, String> identityHashMap = new IdentityHashMap<>(otherMap);

在这种情况下,它使用otherMap的条目初始化创建的identityHashMap。

3.2 添加、检索、更新和删除条目

put()方法用于添加条目:

identityHashMap.put("title", "Harry Potter and the Goblet of Fire");
identityHashMap.put("author", "J. K. Rowling");
identityHashMap.put("language", "English");
identityHashMap.put("genre", "Fantasy");

我们还可以使用putAll()方法添加来自其他Map的所有条目:

identityHashMap.putAll(otherMap);

要检索值,我们使用get()方法:

String value = identityHashMap.get(key);

要更新键的值,我们使用put()方法:

String oldTitle = identityHashMap.put("title", "Harry Potter and the Deathly Hallows");
assertEquals("Harry Potter and the Goblet of Fire", oldTitle);

在上面的代码片段中,put()方法在更新后返回旧值。第二条语句确保oldTitle匹配较早的“title”值。

我们可以使用remove()方法来删除一个元素:

identityHashMap.remove("title");

3.3 遍历所有条目

我们可以使用entitySet()方法遍历所有条目:

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 其他方法

我们也有不同的可用方法,它们的工作方式与其他Map对象类似:

  • clear():删除所有条目
  • containsKey():查找键是否存在于Map中。只有引用是等同的
  • containsValue():查找Map中是否存在该值。只有引用是等同的
  • keySet():返回一个基于标识的键集
  • size():返回条目数
  • values():返回值的集合

3.5 支持空键和空值

IdentityHashMap允许键和值都为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"));

上面的代码片段确保null作为键和值。

3.6 IdentityHashMap并发

IdentityHashMap不是线程安全的,与HashMap相同。因此,如果我们有多个线程并行访问/修改IdentityHashMap条目,我们应该将它们转换为同步Map。

我们可以使用Collections类获取同步Map:

Map<String, String> synchronizedMap = Collections.synchronizedMap(new IdentityHashMap<String, String>());

4. 引用等式的使用示例

IdentityHashMap在equals()方法上使用引用相等性(==)来搜索/存储/访问键对象。

使用四个属性创建的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());

当使用新的字符串对象“genre”作为键时,HashMap将其等同于现有键并更新值。因此,HashMap的大小保持为4。

以下代码片段显示了IdentityHashMap的行为有何不同:

identityHashMap.put(new String("genre"), "Drama");
assertEquals(5, identityHashMap.size());

IdentityHashMap将新的“genre”字符串对象视为新键。因此,它断言大小为5。两个不同的“genre”对象用作两个键,“Drama”和“Fantasy”作为值。

5. 可变键

IdentityHashMap允许可变键,这是该类的另一个有用的特性。

这里我们将一个简单的Book类作为可变对象:

class Book {
    String title;
    int year;

    // other methods including equals, hashCode and toString
}

首先,创建Book类的两个可变对象:

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));

尽管book2条目存在于HashMap中,但它无法检索其值。因为它已经被修改并且equals()方法现在不等同于修改后的对象。这就是为什么一般的Map对象要求不可变对象作为键。

下面的代码片段在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即使在键对象被修改时也能够检索值。在上面的代码中,assertEquals确保再次检索相同的文本。这是可能的,因为引用相等。

6. 一些用例

由于其特性,IdentityHashMap与其他Map对象不同。但是,它不用于一般用途,因此我们在使用此类时需要谨慎。

它有助于构建特定的框架,包括:

  • 维护一组可变对象的代理对象
  • 基于对象引用构建快速缓存
  • 使用引用保存对象的内存图

7. 总结

在本文中,我们了解了如何使用IdentityHashMap、它与一般HashMap的区别以及一些用例。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章