”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > Go 中的地图

Go 中的地图

发布于2024-08-24
浏览:323

Maps in Go

简介

Go 合并了一个本地类型,它实现了称为 map 的哈希表。它是一种数据类型,由唯一键的集合和每个键的值的集合组成。
例如,它可以与其他语言中的字典进行比较,字典存储键值对。这些值是使用键访问的,就像我们在上一篇文章中看到的数组和切片一样。
索引不限于数组或切片中的数字,并且元素没有排序,因此如果我们打印地图,如果我们不执行任何操作来覆盖其打印并强制执行所需的顺序,它将返回随机顺序。

地图声明和初始化

要声明一个映射,它是用map[key]value完成的,其中key将是我们想要的键的类型(它必须是可比较的类型https://go.dev/ref/spec#Comparison_operators ) 和 value 将是我们希望映射存储在每个键中的类型,无论它是什么类型,从 int 到结构,或另一个映射,无论我们想要什么。

与切片一样,映射是引用类型,这意味着映射的零值将为 nil。
发生这种情况是因为它下面有一个存储键和值的哈希表,它们只是它们的一个信封和抽象。

如果我们将其声明为:

var m map[int]int

其值为零。

如果我们希望它的值为零,我们可以使用声明:

m := map[int]int{}

我们甚至可以使用 make 函数像切片一样初始化它。

m := make(map[string]string)

这样做将使用适当的内存池初始化哈希映射,从而返回指向该数据结构的映射。

从地图中添加和读取值

向映射添加值是通过使用大括号 [] 和大括号来完成的,就像数组或切片一样。在此示例中,我们将创建一个映射,其中键为字符串,值为整数,以存储姓名和年龄。

ages := make(map[string]int)

ages["John"] = 33
ages["Charly"] = 27
ages["Jenny"] = 45
ages["Lisa"] = 19

如果我们想在声明映射时向其中添加值,我们可以使用简短声明并在同一步骤中完成所有操作:

ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19}

要读取值,我们只需指示映射的键,它将返回该值。例如,要找出丽莎的年龄,我们可以这样做:

fmt.Println(ages["Lisa"]) // 19

如果我们尝试访问不存在的键,则获得的值将是该类型的零值,在本例中它将是“”,因为它是一个字符串。

为了检查映射中是否存在某个元素,我们可以检查该类型是否为默认类型,但这不是很可靠,因为它可能存在,但其值为空字符串,或者在 int 的情况下为 0 ,它将与其零值匹配,因此 Go 可以帮助我们完成以下操作:

val, ok := ages["Randy"]

如果我们将映射等于两个值,第一个将是通过键访问的该元素的值,在本例中为“Randy”,它不存在,第二个将是一个布尔值,它将指示它是否存在存在与否。

如果我们对值不感兴趣,只是想检查某个键是否存在,我们可以使用 _ 来忽略该值,如下所示:

_, ok := ages["Randy"]

与数组和切片一样,我们可以使用 len 函数来找出映射中有多少个元素。

fmt.Println(len(ages)) // 4

如果我们想要修改一个值,就像使用一个键访问该值并将其与另一个值匹配一样简单,它就会被修改。

如果我们声明第二个映射指向第一个映射,如果我们修改第二个映射的值,因为它是引用类型,我们将修改第一个映射的值,因为两者在下面共享相同的哈希表。

ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19}
agesNew := ages
agesNew["Bryan"] = 77
fmt.Println(agesNew) // map[Bryan:77 Charly:27 Jenny:45 John:33 Lisa:19]
fmt.Println(ages) // map[Bryan:77 Charly:27 Jenny:45 John:33 Lisa:19]

从地图中删除值

为了从map中删除元素,Go为我们提供了一个删除函数,其签名如下:delete(m map[Type]Type1, key Type),它接收一个map和要删除的key。
在前面的例子中,如果我们想消除“Lisa”,我们会这样做:

delete(ages, "Lisa")

循环遍历地图

如果我们想要遍历地图的内容,我们可以使用 for 和我们在数组和切片的帖子中已经看到的范围变化来完成。
这样,第一个元素将是索引,因此是键,第二个元素是值。

for key, value := range ages {
    fmt.Printf("%s: %d\n", key, value)
}

// Output:
// Jenny: 45
// Lisa: 19
// John: 33
// Charly: 27

与数组和切片一样,如果我们只对值感兴趣,而不对键感兴趣,我们可以使用 _.
省略它

for _, value := range ages {
    fmt.Println(value)
}

// Output:
// 19
// 33
// 27
// 45

如果我们感兴趣的只是键,我们可以将范围分配给单个变量来获取它:

for key := range ages {
    fmt.Println(key)
}

// Output:
// John
// Charly
// Jenny
// Lisa

对地图进行排序

正如我在简介中提到的,Map 中的信息是没有顺序的,因此在循环遍历它时我们无法指定它遵循的顺序,Go 也不能保证执行之间的顺序是相同的。
正如我们在数组和切片中看到的那样,标准库中有一个排序包可以帮助我们对元素进行排序:https://pkg.go.dev/sort

按照我们的年龄示例并使用排序,我们可以在遍历地图之前对地图的键进行排序,从而保证它将按顺序访问。

ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19}
keys := make([]string, 0, len(ages))
for k := range ages {
    keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
    fmt.Println(k, ages[k])
}

// Output:
// Charly 27
// Jenny 45
// John 33
// Lisa 19

我们用之前看到的简短声明来声明我们的年龄图。
我们创建一个字符串切片来存储键,并使用长度为 0 的 make 方法,因为我们目前没有任何键,但我们确实使用 len 方法保留了映射长度的容量。
我们遍历年龄映射以保留其键并将它们添加到创建的切片中。
我们使用 sort.Strings 函数按字母顺序对键进行排序。
我们检查已经订购的钥匙片,并使用有问题的钥匙访问地图。
这样我们就可以有序地访问地图,并且可以执行程序所需的逻辑。

并发问题

使用地图时需要记住的一点是它们同时使用是不安全的。如果这些是并发读取,无论是访问一个值还是通过带有范围的 for ,多个 goroutine 同时访问它都没有问题。
有问题的情况是,例如,当您想要通过添加或删除元素来同时更新映射的值,同时从另一侧读取它时。
为了解决这种情况,有几种可能的解决方案,我不会详细介绍,我只会简单地提及并让您选择更深入地研究它们。

如果我们使用标准库中的sync包:https://pkg.go.dev/sync,我们可以控制不同goroutines之间的同步。
一个可能的用途是 RWMutex 类型,它允许我们锁定和解锁对类型的读取和写入。因此,如果我们有一个包含sync.RWMutex和映射的类型,我们可以控制何时可以访问它。
在同一个同步包中研究的另一个有趣的类型是 Map,它已经为我们提供了一系列可以帮助我们使用地图的函数,但最终我们将无法像之前的解决方案一样在本地使用它。
根据我们正在实现的用例,其中一个对我们更有用,并且没有一个比另一个更好,这始终取决于我们的需要。

我希望我在这篇文章中试图解释的一切都已经很清楚了,如果有任何部分还没有完全清楚,或者有一些我没有涵盖但您希望我做的部分,请离开您可以在这里或通过我的社交网络对我的个人资料发表评论,我很乐意回复。

版本声明 本文转载于:https://dev.to/charly3pins/maps-in-go-5a3j?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何在 MySQL 中将行转置为列:综合指南
    如何在 MySQL 中将行转置为列:综合指南
    在 MySQL 中将行转换为列在 MySQL 查询中将行转换为列需要在应用程序中执行复杂的查询或手动操作。GROUP_CONCAT 解决方案虽然 GROUP_CONCAT 可以将行转换为单列,但它不提供整个结果集所需的转置。手动查询方法对于更复杂的转置,需要细致的查询,从原始行手动构造每一列。复杂查...
    编程 发布于2024-11-07
  • 如何解决iOS后台模式下未收到GCM通知的问题
    如何解决iOS后台模式下未收到GCM通知的问题
    当应用程序在 iOS 上处于后台模式时未收到 GCM 通知当 iOS 在后台收到通知但不处理时,会出现此问题它们在用户界面中。要解决此问题,请确保您的应用:启用后台推送通知:检查您的应用是否已请求并获得在后台接收推送通知的权限。设置徽章应用程序图标:验证是否在应用程序的“设置”>“通知”部分中选择了...
    编程 发布于2024-11-07
  • 为什么在 Windows 7 中使用 CLASSPATH 时出现 ClassNotFoundException?
    为什么在 Windows 7 中使用 CLASSPATH 时出现 ClassNotFoundException?
    尽管使用 CLASSPATH 环境变量仍解决 java.lang.ClassNotFoundException在 Windows 7 中尝试使用 Java 连接到 MySQL 数据库时,设置 CLASSPATH 环境变量以包含 JDBC 驱动程序 jar 文件的路径似乎无法解决 java.lang....
    编程 发布于2024-11-07
  • 开发人员需要了解免费外汇 API
    开发人员需要了解免费外汇 API
    如果您是一名开发人员,您一定正在寻找可以帮助您更轻松地工作的工具,对吗?免费的外汇 API 就是其中之一!它使您无需支付任何费用即可获取外汇汇率。但是,许多开发人员对这些 API 不太了解。因此,本文旨在解释什么是免费外汇 API、它为何有用以及如何为您的项目选择一个 API。 什么是免费外汇 A...
    编程 发布于2024-11-07
  • 如何使用 JavaScript 将字符串中每个单词的首字母大写?
    如何使用 JavaScript 将字符串中每个单词的首字母大写?
    使用 JavaScript 将字符串中每个单词的首字母大写在 JavaScript 中,将字符串中每个单词的首字母大写可以可以通过多种方法来实现。一种常见的方法是使用将给定字符串转换为标题大小写的函数。让我们探索一个演示此技术的代码示例:function titleCase(str) { var...
    编程 发布于2024-11-07
  • 我们能否在 JavaScript 中实现超越“setTimeout()”的可靠计时器精度?
    我们能否在 JavaScript 中实现超越“setTimeout()”的可靠计时器精度?
    在 Javascript 中实现超越 setTimeout() 的计时器精度Javascript 的 setTimeout() 方法在精度方面经常达不到要求,表现出不可预测的延迟,可能会影响 UI 操作。因此,开发人员可能想知道是否有其他方法可以提供更可靠的计时功能。使用 setTimeout() ...
    编程 发布于2024-11-07
  • 使用 Amazon Q Transformation 将 Java 颂歌转换为 Java
    使用 Amazon Q Transformation 将 Java 颂歌转换为 Java
    近年来,Java 取得了显着的进步,每个新版本都引入了强大的功能和优化。如果您仍在 Java 8 上运行,您就会错过性能、语法和安全性方面的重大改进。从 Java 8 升级到 Java 17 似乎令人畏惧,但 Amazon Q 的转换功能通过自动化一些较繁琐的步骤使升级变得更加容易。在这篇文章中,我...
    编程 发布于2024-11-07
  • 使用 React 构建食谱查找器网站
    使用 React 构建食谱查找器网站
    Introduction In this blog, we'll be building a Recipe Finder Website using React. This app allows users to search for their favorite recipes,...
    编程 发布于2024-11-07
  • Turborepo 与 Nx:哪种 Monorepo 工具适合您?
    Turborepo 与 Nx:哪种 Monorepo 工具适合您?
    随着现代开发变得越来越复杂,monorepos变得越来越流行。它们允许将多个项目或包存储在单个存储库中,从而简化依赖关系管理并促进更好的协作。用于管理 monorepos 的两个顶级工具是 Turborepo 和 Nx。 这两种工具都旨在提高处理单一存储库的效率和可扩展性,但它们具有独特的优势。在本...
    编程 发布于2024-11-07
  • Java 数组简介
    Java 数组简介
    编程通常涉及管理和操作大量数据,对此高效且有效的数据结构至关重要。数组是计算机科学中的基本数据结构,提供了一种存储固定大小的相同类型元素序列的方法。在本博客中,我们将深入了解 Java 中的数组:了解它们是什么、它们的语法、如何对它们进行操作以及它们的内存管理。 为什么我们需要数组?...
    编程 发布于2024-11-07
  • 解决 CORS 问题的方法
    解决 CORS 问题的方法
    要解决 CORS 问题,您需要在 Web 服务器(如 Apache 或 Nginx)、后端(如 Django、Go 或 Node.js)中添加适当的标头,或在前端框架(如 React 或 Next.js)中。以下是每个平台的步骤: 1. 网络服务器 阿帕奇 您可以在 ...
    编程 发布于2024-11-07
  • 内存对齐如何影响 C 结构的大小?
    内存对齐如何影响 C 结构的大小?
    C 结构中的内存对齐使用 C 结构时,理解内存对齐至关重要。内存对齐是指将数据在内存中放置在特定的边界处。在 32 位机器上,内存通常按 4 字节边界对齐。结构的内存对齐考虑以下结构:typedef struct { unsigned short v1; unsigned short...
    编程 发布于2024-11-07
  • 受顶级旅游景点启发构建创新项目:令人难忘的旅行体验开发人员指南
    受顶级旅游景点启发构建创新项目:令人难忘的旅行体验开发人员指南
    作为开发商,我们经常从周围的世界中汲取灵感——还有什么比令人难以置信的旅游景点更好的来源呢?无论您是在开发旅行应用程序、沉浸式体验还是基于位置的服务,了解目的地的脱颖而出都是关键。看看这份关于阿尔巴尼亚最佳旅游景点的终极指南,为您的下一个创意项目提供动力,并了解这些地标如何在现实世界和数字世界中塑造...
    编程 发布于2024-11-07
  • 如何使用 std::locale 在 C++ 中使用逗号格式化数字?
    如何使用 std::locale 在 C++ 中使用逗号格式化数字?
    在 C 中用逗号格式化数字 在 C 中,std::locale 类提供了一种依赖于区域设置的方式用逗号格式化数字.std::locale 与 std::stringstream要将数字格式化为带逗号的字符串,可以将 std::locale 与 std::stringstream 一起使用如下:#in...
    编程 发布于2024-11-07
  • 如何避免在 Python 中打印素数序列中的奇数?
    如何避免在 Python 中打印素数序列中的奇数?
    如何在 Python 中打印素数序列许多程序员都在努力创建一个在 Python 中准确打印素数的函数。一个常见的问题是打印奇数列表。要解决这个问题,必须彻底了解素数属性并修改代码。素数只能被 1 和它们本身整除。因此,改进的代码检查从 2 到数字的平方根(如果数字较小,则为数字本身)范围内的整除性。...
    编程 发布于2024-11-07

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3