使用 Map 存储键值对
介绍 Map 层次结构
集合框架提供的第二个主要结构是经典数据结构的实现:哈希表结构。这个概念并不新鲜,并且是数据结构化的基础,无论是在内存中还是不在内存中。它是如何工作的,它如何在集合框架中实现?
哈希表是一种能够存储键值对的结构。值是您的应用程序需要处理的任何对象,键是代表该对象的任何东西。
假设您需要创建一个应用程序来处理发票,这些发票由 Invoice
类实例表示。那么您的值就是这些 Invoice
实例,您的键可以是发票号。每个发票都有一个号码,并且该号码在所有发票中都是唯一的。
一般来说,每个值都绑定到一个键,就像发票绑定到其发票号一样。如果您有一个给定的键,您可以检索该值。通常,键是一个简单的对象:想想一个包含多个字符的字符串或一个数字。另一方面,值可以像您需要的那样复杂。这就是哈希表存在的意义:您可以操作键,将它们从应用程序的一部分移动到另一部分,通过网络传输它们,当您需要完整对象时,您可以使用其键检索它。
在您看到 Map
接口的所有细节之前,以下是一些您需要牢记的概念。
- 哈希表可以存储键值对
- 键充当给定值的符号
- 键是一个简单的对象,值可以像需要的那样复杂
- 键在哈希表中是唯一的,值不必是唯一的
- 存储在哈希表中的每个值都必须绑定到一个键,Map 中的键值对构成该 Map 的一个条目
- 键可用于检索其绑定的值。
集合框架为您提供了一个 Map
接口来实现这个概念,以及两个扩展,SortedMap
和 NavigableMap
,如下图所示。
这个层次结构非常简单,看起来像 Set
层次结构,带有 SortedSet
和 NavigableSet
。实际上,SortedMap
与 SortedSet
具有相同的语义:SortedMap
是一个 Map,它按键对键值对进行排序。对于 NavigableMap
也是如此:此接口添加的方法与 NavigableSet
添加到 SortedSet
的方法相同。
JDK 为您提供了 Map
接口的几种实现,最广泛使用的是 HashMap
类。
以下是另外两种实现。
LinkedHashMap
是一个具有内部结构以保持键值对排序的HashMap
。迭代键或键值对将遵循添加键值对的顺序。IdentityHashMap
是一个专门的Map
,您应该只在非常精确的情况下使用它。此实现并非旨在普遍用于应用程序。它不使用equals()
和hashCode()
来比较键对象,而是仅使用相等运算符 (==
) 比较这些键的引用。谨慎使用,只有在您确定这就是您需要的时才使用它。
您可能听说过多重映射。多重映射是一个概念,其中单个键可以与多个值相关联。集合框架不支持此概念。但是,此功能可能很有用,您将在本教程的后面看到如何创建值实际上是值列表的映射。此模式允许您创建类似多重映射的结构。
使用集合的便利工厂方法创建 Map
如您所见,Java SE 9 在 List
和 Set
接口中添加了方法来创建不可变列表和集合。
在 Map
接口上也有这样的方法,可以创建不可变映射和不可变条目。
您可以使用以下模式轻松创建 Map。
Map<Integer, String> map =
Map.of(
1, "one",
2, "two",
3, "three"
);
不过,有一个警告:只有在键值对不超过 10 个的情况下才能使用此模式。
如果您有更多,则需要使用其他模式
Map.Entry<Integer, String> e1 = Map.entry(1, "one");
Map.Entry<Integer, String> e2 = Map.entry(2, "two");
Map.Entry<Integer, String> e3 = Map.entry(3, "three");
Map<Integer, String> map = Map.ofEntries(e1, e2, e3);
您也可以用这种方式编写此模式,并使用静态导入来进一步提高其可读性。
Map<Integer, String> map3 =
Map.ofEntries(
Map.entry(1, "one"),
Map.entry(2, "two"),
Map.entry(3, "three")
);
对于这些工厂方法创建的映射和条目,与集合一样,存在一些限制
- 您获得的映射和条目是不可变对象
- 不允许使用空条目、空键和空值
- 尝试以这种方式创建具有重复键的映射没有意义,因此,作为警告,您将在映射创建时收到
IllegalArgumentException
。
在 Map 中存储键值对
键与其绑定值之间的关系遵循以下两个简单规则。
- 一个键只能绑定到一个值
- 一个值可以绑定到多个键。
这会导致 Map 内容的几个后果。
- 所有键的集合不能有任何重复项,因此它具有
Set
的结构 - 所有键值对的集合也不能有任何重复项,因此它也具有
Set
的结构 - 所有值的集合可能包含重复项,因此它具有普通
Collection
的结构。
然后,您可以对 Map 定义以下操作
- 将键值对放入 Map 中。如果键已在 Map 中定义,则此操作可能会失败
- 从键获取值
- 从 Map 中删除键,以及其值。
您还可以定义经典的集合式操作
- 检查 Map 是否为空
- 获取 Map 中包含的键值对的数量
- 将另一个 Map 的所有内容放入此 Map 中
- 清除 Map 的内容。
所有这些操作和概念都在 Map
接口中实现,以及您将在后面看到的其他一些操作。
探索 Map 接口
Map
接口是 JDK 中对 Map 概念进行建模的基本类型。
在选择 Map 的键类型时,您应该格外小心。简而言之,选择可变键并不禁止,但很危险,不建议这样做。一旦键被添加到 Map 中,对其进行变异可能会导致其哈希码值及其标识发生变化。这可能会使您的键值对无法恢复,或者在查询 Map 时可能会得到不同的值。您将在后面的示例中看到这一点。
Map
定义了一个成员接口: Map.Entry
来对键值对进行建模。此接口定义了三个方法来访问键和值
getKey()
:读取键;getValue()
和setValue(value)
:读取和更新绑定到该键的值。
您可以从给定 Map 中获得的 Map.Entry
对象是 Map 内容的视图。因此,修改条目对象的 value 会反映在 Map 中,反之亦然。这就是您无法更改此对象中的键的原因:它可能会破坏您的 Map。
上次更新: 2021 年 9 月 14 日