购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.4 映射类型

切片在处理一组连续数据时非常有用。像大多数编程语言一样,Go也提供了一种内置数据类型,用于当你想要将一个值与另一个值相关联的情况。映射类型被写作map[keyType] valueType。让我们来看几种声明映射的方法。首先,你可以使用var声明来创建一个被设置为零值的映射变量:

在这种情况下,nilMap被声明为一个键为string类型、值为int类型的映射。映射的零值是nil。一个nil映射的长度为0。尝试读取一个nil映射总是返回映射值类型的零值。然而,尝试向一个nil映射变量写入数据会导致程序崩溃。

你可以使用:=声明,该方法通过映射字面量来创建一个映射并赋值给变量:

在这种情况下,你正在使用一个空的映射字面量。这和nil映射不一样。它的长度为0,但是对通过空映射字面量定义的映射进行读和写都是可以的。下面是一个非空映射字面量:

一个映射字面量的主体以键开头,后面跟一个冒号(:),然后是值。在映射中,每一对键值对之间以逗号分隔,即使是在最后一行。在这个例子中,值是一个字符串类型的切片。映射的值的类型可以是任意的。对于键的类型有一些限制,这一点我稍后会讨论。

如果你知道打算在映射中放入多少键值对,但不知道确切的值,你可以使用make函数来创建一个具有默认大小的映射:

使用make函数创建的映射的长度仍然为0,并且长度可以超出初始定义的长度。

映射在几个方面类似于切片:

·当你往其中添加键值对时,映射会自动增长。

·如果你知道打算向映射中插入多少键值对,可以使用make函数创建一个具有特定初始大小的映射。

·将映射传入len函数会得到映射中键值对的数量。

·映射的零值是nil。

·映射是不可比较的。你可以检查它们是否等于n i l,但你不能使用==来检查两个映射是否拥有相同的键和值,也不能使用!=来检查它们是否不同。

映射的键可以是任何可比较的类型。这意味着你不能使用切片或映射作为映射的键。

何时应该使用映射,何时应该使用切片呢?当需要按顺序处理数据或者元素的顺序很重要时,你应该对这些数据使用切片。

映射在需要使用除递增整数值以外的其他内容来组织值时非常有用,比如姓名。

什么是哈希映射?

在计算机科学中,map是一种数据结构,它将一个值与另一个值关联(或映射)。map可以通过几种不同的方式实现,每种方式都有其权衡。Go语言内置的map就是一种哈希(hash)映射或哈希表。如果你不熟悉这个概念,Aditya Bhargava所著的 Grokking Algorithms https://oreil.ly/A5GzB )第5章介绍了什么是哈希表以及为什么它们如此有用。

Go语言内置哈希映射的实现并作为运行时的一部分,这是很棒的,因为自己构建一个正确的哈希映射很难。如果你想了解更多关于Go是如何做到的,可以观看Keith Randall在2016年GopherCon上的演讲“Inside the Map Implementation”( https://oreil.ly/kIeJM )。

Go语言不要求(甚至不允许)你定义自己的哈希算法或等价定义。相反,Go的运行时会对所有允许作为键的类型进行哈希算法处理。

3.4.1 映射的读写操作

我们来看一个简短的程序,看看如何声明、写入和读取映射。你可以在The Go Playground( https://oreil.ly/gBMvf )上运行示例3-10,或者在第3章代码仓库的 sample_code/map_read_write 目录( https://oreil.ly/Ur5f2 )中运行。

示例3-10:映射的使用

当你运行这个程序时,你会看到以下输出:

你通过将键置于括号内并使用=指定值来为一个映射键分配一个值,通过将键置于括号内来读取分配给映射键的值。注意,你不能使用:=为一个映射键分配值。

当你尝试读取从未设置过的映射键分配的值时,映射会返回该映射的值类型的零值。在上面的示例中,值类型是整型,所以你会得到一个0。你可以使用++操作符来增加映射键关联的数值。因为映射默认返回其零值,即使没有与键相关联的现有数值,这也是可行的。

3.4.2 逗号ok模式

如你所见,如果你请求一个不存在于映射中的键所关联的值,映射将返回零值。这在实现像你之前看到的totalWins计数器这样的功能时很方便。然而,有时你确实需要确定一个键是否存在于映射中。Go提供了逗号ok模式(comma ok idiom),来检测键存在但对应零值还是这个键不存在:

与将从映射读取的结果分配给单个变量不同,使用逗号ok模式将从映射中读取的结果分配给两个变量。第一个变量得到了键对应的值,第二个则为布尔值,通常这个变量名为ok。如果ok为true,表示键存在于映射中。如果ok为false,则表示键不存在。在这个例子中,代码输出5 true、0 true和0 false。

当你想区分读取一个值与获得零值时,在Go中使用逗号ok模式。当你在第12章从通道中读取数据时,以及你在第7章使用类型断言时,你将再次看到它。

3.4.3 从映射中删除键值对

通过内置的delete函数可以从映射中移除键值对:

delete函数接受一个映射和一个键作为参数,然后移除该键对应的键值对。如果键不存在于映射中,或者映射为nil,就不会发生任何事情。delete函数不会返回任何值。

3.4.4 清空映射

你在3.2.5节中看到的clear函数也适用于映射。一个被清空的映射的长度被设置为0,这与被清空的切片不同。以下代码:

输出结果为:

3.4.5 比较映射

Go 1.21在标准库中新增了一个叫作maps的包,其中包含一些用于操作映射的辅助函数。在8.12节中,你将了解到关于这个包的更多信息。该包中用于比较两个映射是否相等的两个函数非常有用,它们是maps.Equal和maps.EqualFunc。它们与slices.Equal和slices.EqualFunc函数相对应:

3.4.6 映射模拟集合

许多语言在其标准库中包含集合。集合是一种数据类型,它确保某个值最多只出现一次,但不保证这些值有特定的顺序。检查某个元素是否在集合中是很快的,无论集合中有多少元素。(相比之下,当你向切片中添加更多元素时,检查一个元素是否在切片中会耗时更长。)

Go语言没有包含集合,但你可以使用映射来模拟它的一些功能。使用映射的键来表示你想放入集合的类型,并使用布尔值作为值。示例3-11中的代码演示了这一概念。你可以在The Go Playground( https://oreil.ly/wC6XK )上运行它,或在第3章代码仓库中的 sample_code/map_set 目录( https://oreil.ly/Ur5f2 )中运行。

示例3-11:映射模拟集合

你需要一个整数集合,所以你创建了一个键为int类型、值为bool类型的映射。你使用for-range循环(将在4.4.5节中讨论)遍历vals中的值,将它们放入intSet中,并将每个int值与布尔值true关联起来。

我们向intSet中写入了11个值,但是intSet的长度是8,这是因为在映射中不能有重复的键。如果你在intSet中查找5,它会返回true,因为有一个键为5。然而,如果你在intSet中查找500或者100,会返回false。这是因为你没有将这些值放入intSet,这导致映射返回映射值的零值,而布尔型的零值就是false。

如果你需要提供诸如并集、交集和差集操作的集合,你可以自己编写一个,或者使用许多提供此功能的第三方库。(你将在第10章中学习更多关于使用第三方库的内容。)

有些人更喜欢在使用映射实现集合时将值类型设置为struct{}。(我将在下一节中讨论结构体。)它的优点是空结构不占用任何字节数,而布尔类型占用一个字节。

缺点是使用空结构体(struct{})会使你的代码显得较为笨拙。赋值操作不太直观,并且需要使用逗号ok模式来检查一个值是否在集合中:

除非你有非常大的集合,否则内存占用的差异可能不够显著,不足以抵消其缺点。 CsgmX/TZ6KU4ll6n9XN4AetWSO8rrxs32QkAPKdAYA/0XatTTtisP/PwlKY6kKTS

点击中间区域
呼出菜单
上一章
目录
下一章
×