系列中的上一篇
当前教程
使用 Map 存储键值对
系列中的下一篇

系列中的上一篇: 在堆栈和队列中存储元素

系列中的下一篇: 管理 Map 的内容

使用 Map 存储键值对

 

介绍 Map 层次结构

集合框架提供的第二个主要结构是经典数据结构的实现:哈希表结构。这个概念并不新鲜,并且是数据结构化的基础,无论是在内存中还是不在内存中。它是如何工作的,它如何在集合框架中实现?

哈希表是一种能够存储键值对的结构。值是您的应用程序需要处理的任何对象,键是代表该对象的任何东西。

假设您需要创建一个应用程序来处理发票,这些发票由 Invoice 类实例表示。那么您的值就是这些 Invoice 实例,您的键可以是发票号。每个发票都有一个号码,并且该号码在所有发票中都是唯一的。

一般来说,每个值都绑定到一个键,就像发票绑定到其发票号一样。如果您有一个给定的键,您可以检索该值。通常,键是一个简单的对象:想想一个包含多个字符的字符串或一个数字。另一方面,值可以像您需要的那样复杂。这就是哈希表存在的意义:您可以操作键,将它们从应用程序的一部分移动到另一部分,通过网络传输它们,当您需要完整对象时,您可以使用其键检索它。

在您看到 Map 接口的所有细节之前,以下是一些您需要牢记的概念。

  • 哈希表可以存储键值对
  • 键充当给定值的符号
  • 键是一个简单的对象,值可以像需要的那样复杂
  • 键在哈希表中是唯一的,值不必是唯一的
  • 存储在哈希表中的每个值都必须绑定到一个键,Map 中的键值对构成该 Map 的一个条目
  • 键可用于检索其绑定的值。

集合框架为您提供了一个 Map 接口来实现这个概念,以及两个扩展,SortedMapNavigableMap,如下图所示。

The Map Interface Hierarchy

Map 接口层次结构

这个层次结构非常简单,看起来像 Set 层次结构,带有 SortedSetNavigableSet。实际上,SortedMapSortedSet 具有相同的语义:SortedMap 是一个 Map,它按键对键值对进行排序。对于 NavigableMap 也是如此:此接口添加的方法与 NavigableSet 添加到 SortedSet 的方法相同。

JDK 为您提供了 Map 接口的几种实现,最广泛使用的是 HashMap 类。

以下是另外两种实现。

  • LinkedHashMap 是一个具有内部结构以保持键值对排序的 HashMap。迭代键或键值对将遵循添加键值对的顺序。
  • IdentityHashMap 是一个专门的 Map,您应该只在非常精确的情况下使用它。此实现并非旨在普遍用于应用程序。它不使用 equals()hashCode() 来比较键对象,而是仅使用相等运算符 (==) 比较这些键的引用。谨慎使用,只有在您确定这就是您需要的时才使用它。

您可能听说过多重映射。多重映射是一个概念,其中单个键可以与多个值相关联。集合框架不支持此概念。但是,此功能可能很有用,您将在本教程的后面看到如何创建值实际上是值列表的映射。此模式允许您创建类似多重映射的结构。

 

使用集合的便利工厂方法创建 Map

如您所见,Java SE 9 在 ListSet 接口中添加了方法来创建不可变列表和集合。

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 来对键值对进行建模。此接口定义了三个方法来访问键和值

您可以从给定 Map 中获得的 Map.Entry 对象是 Map 内容的视图。因此,修改条目对象的 value 会反映在 Map 中,反之亦然。这就是您无法更改此对象中的键的原因:它可能会破坏您的 Map。


上次更新: 2021 年 9 月 14 日


系列中的上一篇
当前教程
使用 Map 存储键值对
系列中的下一篇

系列中的上一篇: 在堆栈和队列中存储元素

系列中的下一篇: 管理 Map 的内容