diff --git a/chap-22-Maps/chapter_22.md b/chap-22-Maps/chapter_22.md new file mode 100644 index 0000000000000000000000000000000000000000..414acebf989e9592be61aa564a08b6015e9fea96 --- /dev/null +++ b/chap-22-Maps/chapter_22.md @@ -0,0 +1,660 @@ +# Chapter 22: Maps +![Maps](./imgs/maps.a1e5819d.jpg) + +## 1 你将在本章学到什么 + +- map 是什么? +- 什么是 key, 什么是 value? +- 如何创建一个 map。 +- 如何在 map 中插入一个 entry? +- 如何从 map 中获取entry。 + +## 2 涵盖的技术概念 + +- Map 类型 +- Key-Value 对(键值对) +- Map entry +- 哈希表 +- 时间复杂度 + +## 3 我们为什么需要 Map ? +在本节中,我们将详细介绍 map 是如何工作的。但首先,让我们花一些时间,通过一个例子来理解为什么这个数据结构是有用的: +### 3.0.0.1 使用 slice +```go +// maps/without-maps/main.go +package main + +import "fmt" + +type testScore struct { + studentName string + score uint8 +} + +func main() { + results := []testScore{ + {"John Doe", 20}, + {"Patrick Indexing", 15}, + //... + //... + {"Bob Ferring", 7}, + {"Claire Novalingua", 8}, + } + fmt.Println(results) +} +``` +我们有一个类型结构体 `testScore` 和一个由 `testScore` 元素组成的 `results` slice。现在让我们假设我想要检索一个叫 Claire Novalingua 的学生的分数。由于我们使用了一个 slice ,我们必须遍历每个元素来找到被搜索的项: +```go +for _, result := range results { + if result.studentName == "Claire Novalingua" { + fmt.Println("Score Found:", result.score) + } +} +``` +为什么这个解决方案不是最优的? + +- 我们必须潜在地遍历切片的所有元素。想象一下你的 slice 包含了数千个元素!这对性能会产生很大影响。 +- 所写的代码不短。我们使用 for 循环范围和嵌套比较。这五行代码不容易读懂。 + +## 4 Map 是什么? +Map 是类型为 T 的元素的无序集合,由类型 U 的唯一键进行索引。 + +### 4.0.0.1 例子: +![Maps](./imgs/map_definition.0dfd1bb3.png) + +Map 例子 + +在前面的图中,我们有一张代表足球世界杯冠军的 map 。这里的 key 是年份(类型为 `uint8` ), value 为获胜国家名(类型为 `string` )。map 类型表示为: +```go +map[uint8]string +``` +map 的元素称为 "map entry"。它通常也被命名为 key-value pair (键-值对)。 + +### 4.0.0.2 一般定义 +```go +map[keyType]elementType +``` +使用 map ,你可以执行以下操作: + +- 使用特定键 (key) 存储值 (value) +- 删除与特定键 (key) 一起存储的值 (value) +- 检索与特定键 (key) 一起存储的值 (value) + +我们再举一个例子:字典可以使用 map 来存储。在字典中,我们存储的单词的定义。在本例中,定义是 map 中的值 (value),单词表示键 (key)。当你使用字典时,你搜索一个特定的单词来得到它的定义。我们不会根据定义查字典,这种类型的查找可能会花费你很多时间,因为定义没有被索引。我们可以把这个类比用在 map 上。我们总是根据一个特定的键 (key) 进行查找,map 是按键 (key) 建立索引的。 + +我们能否将Go中定义的所有类型都放到 map 中作为键 (key) 类型?值 (value) 类型呢? + +## 5 keys: 允许使用的类型 +你不能将任何类型都作为 key 来使用。有一个限制:“作为key的类型必须完全支持比较操作符 `==` 和 `!=` 的运算” 。因此我们排除了哪些类型? + +- function +- map +- slice +- 由 function,map 或 slice 组成的数组 +- 包含 function, map 或 slice 的字段的结构体类型 + +```go +// FORBIDDEN: an array of slices +[3][]int + +// FORBIDDEN : an array of functions +[3]func(http.ResponseWriter, *http.Request) + +// FORBIDDEN: a type with a slice field +type Test struct { + scores []int +} +//... +``` + +## 6 key 必须是不同的 +map 中的 key 必须是不同的。 + +我们可以想象,map 就像一个有许多锁着的门的走廊。每扇门背后,都有一个 value 。打开门的钥匙 (key) 是独一无二的(你可以复制钥匙,但钥匙的样式是一样的)。每把钥匙都能打开一扇门。钥匙和门是 1:1 的关系。 + +## 7 Element +Element 是存储在 map 上的内容。对于 element,没有关于类型的限制。你可以存储任何你想要的东西。你还可以将另一个 map 存储到一个值中。 例如,一个 element 可以是一个年份,一场比赛的分数,一个表示某应用程序的用户的结构体… + +## 8 如何建立一个 map +### 8.1 使用内置函数 make + +你可以使用 `make` 内置函数来分配和初始化一个新 map: +```go +m:=make(map[string]int) +``` + +`m` 会是一个 `map[string]int` 类型的值,这被称为 map 值,在内部它是一个指向哈希表的指针。我们将在下一节中了解什么是哈希表,所以现在不要担心它。 + +### 8.2 使用 map 字面量语法 +使用前面的语法,我们初始化并分配 map 。但是我们没有填充它。我们可以使用 map 字面量语法直接填充它: +```go +worldCupWinners := map[int]string{ + 1930: "Uruguay", + 1934: "Italy", + 1938: "Italy", + 1950: "Uruguay"} +fmt.Println(worldCupWinners) +//map[1930:Uruguay 1934:Italy 1938:Italy 1950:Uruguay] +``` +在前面列出的代码中,我们创建了一个名为 `worldCupWinners` 的 map 。这个 map 直接由四个条目填充。世界杯足球比赛的前四名冠军。这里的键是整数;它们代表年份。这些值是字符串,表示在给定年份赢得奖杯的国家的名称。1930年是乌拉圭赢得了奖杯。 + +请注意,值可以重复。意大利和乌拉圭的值重复了两次,这是完全可以的。 + +还请注意,在初始化 map 之后,还可以向其添加新值。在我们的示例中,我们可以向 map 中添加另一年! + +你还可以使用 map 字面量语法来创建一个空 map。 + +```go +a := map[int]string{} +``` + +在前面列出的代码中,a是一个map(已初始化并已分配),但是其中没有存储键值对。 + +## 9 什么是哈希表 (hash table) ? +下面是哈希表工作原理的简化视图。(go的实现略有不同): +![Maps](./imgs/hash_table.3a049d36.png) +hash table + +一个哈希表由以下元素组成: + +- **一个哈希函数**。它的作用是将键转换为唯一标识符。例如,键 1930 将被传递给哈希函数,它将返回 "4" 。 +- **索引存储**。用于在内存中保存值的一种索引存储。存储最终会以**桶**的形式进行组织,每个**桶**将存储特定数量的值。 + +[comment]: <> (上面这里The storage is eventually organized in buckets. 翻译起来不是很顺畅) + +当我们向哈希表添加键值对时,算法将执行以下步骤: + +1. 使用 `key` 获取 `hash_function(key)` 的返回值(我们使用 `h` 表示返回值)。 `h` 是存储数据的索引(例如,4)。 +2. 将值存储到容器中索引为 `h` 的位置。 + +从给定的 key 中检索 value 值也将使用哈希函数 + +1. `hash_function(key)`会将key的值转化为对应容器的索引并返回。 +2. 使用索引从容器中取出数据并将其返回给用户。 + +[comment]: <> (这里不太对的感觉……) + +### 9.1 一个好的哈希函数 (hash function) + +一个好的哈希函数必须具备以下特性: + +- 避免哈希碰撞: + + - 如果你将键 `1989` 传递给哈希函数,它将返回一个值,例如 `i`。 `i` 将索引的存储值与 `1989` 挂钩。 + - 试想如果现在 `1938` 传递给哈希函数同样返回了 `i` ! + - 当你用键 `1989` 存储一些东西时,它将删除已经为键 `1938` 存储的内容。 + - 想象一下这种碰撞会产生的混乱!例如,hash函数MD5可能产生冲突。(更多信息,请阅读文章[@stevens2006fast](#ref)) + +- 索引的计算要在有限的时间内得到数据的位置。(哈希函数必须是高效的) +- 产生的哈希必须及时稳定。在每次调用时,该键应该产生相同的哈希。 + +## 10 哈希表的时间复杂度 + +- 算法的复杂度是指在机器上运行它所需要的资源数量。 +- 时间复杂度是复杂度的一种;它标明了运行一个程序所需的计算机时间。 + +时间复杂度将取决于哈希表的实现,但是请记住,搜索值和插入新键值对的时间复杂度非常低。 + +下面的时间复杂度通常适用于哈希表: + +- 插入:`O(1)` +- 搜索:`O(1)` + +对于包含三个元素的 map 和包含300万个元素的 map ,搜索和插入将使用相同数量的基本操作! + +我们说它是一个**常量时间**算法。我们还说它是 **order 1**, 这里用的是大 `O` 符号。 + +## 11 内部操作:哈希表实现 + +这是一个关于 map 在Go中是如何实现的概述。内部实现可能随着时间的推移而改变。 + +源代码位于运行时包(runtime/map.go)中。 + +- 一个 Go 中的 map 是一个 **"buckets"** 的数组 +- 一个 **bucket** 最多包含8个键/元素对(也称为8个条目 (entry) )。 +- 每个桶都由一个数字 (id) 标识。 + +### 11.0.0.1 查找一个元素 + +- 为了查找 map 中的一个元素,用户需要一个键 (key) 。 + - Key: "1930" +- 这个键会被传递给**哈希函数**,它会返回一个**哈希值**(一个整数)。 +- 该哈希值包含桶 id 。哈希函数不直接返回桶的 id ,返回值 `h` 必须进行转换以获得桶的 id 。 + - Bucket id = 3 +- 知道桶 id 后,下一步就是在桶中找到正确的条目。这通过比较给定的键和“桶中所有的键”来完成。 + + - Key: "1930" 。Go将遍历 bucket 的键并返回相应的元素。 + +### 11.0.0.2 插入一个元素 +- 用户提供键和元素值 + + - 例如:Key: "1930" - Element: "Uruguay" + +- **key** 被传递给哈希函数。哈希函数将返回哈希值。 +- 根据哈希值,我们可以获取到桶 id +- 然后 Go 将遍历桶元素,以找到存储键和元素的位置。 + + - 当键已经存在时,Go将覆盖元素的值。 + +![Maps](./imgs/go_hashmap.c706cf7b.png) +Go hashmap 实现 + +## 12 使用示例 +在本节中,我们将看一下你可以在 map 上进行的最常见的操作。 为此,我们将使用一个例子。 + +### 12.0.0.1 示例应用 + +- 你被要求为人力资源部门构建一个应用程序 +- 在 alpha 版本中,我们将通过CSV文件加载员工列表 +- 用户需要通过员工的 `employeeId` (由字母和数字组成)查询员工 + + - 例如:V45657 ,V45658... + +下面是CSV文件的摘录: +``` +employeeId,employeeName,genre,position +V45657,John Ollivero,M,CEO +V45658,Frane Elindo,F,CTO +V6555,Walter Van Der Bolstenberg,M,Sales Manager +``` + +### 12.0.0.2 为什么使用 map ? +用户将根据员工的唯一 Id **查询**员工。 + +- 我们将根据唯一的 key 查询员工 +- 这个id不是一个整数;我们可以用 slice 或 map 。 + +我们会使用一个 map ,并且我们会创建一个`employee`类型。 + +- Keys: employeeId => string +- Elements : 类型为`employee`的值 + +### 12.0.0.3 从CSV读取数据 + +让我们构建脚本的第一部分(将数据读入文件) + +```go +// maps/reading-csv/main.go +package main + +import ( + "encoding/csv" + "fmt" + "io" + "log" + "os" +) + +func main() { + file, err := os.Open("/Users/maximilienandile/Documents/DEV/goBook/maps/usages/employees.csv") + if err != nil { + log.Fatalf("impossible to open file %s", err) + } + + defer file.Close() + + r := csv.NewReader(file) + for { + record, err := r.Read() + if err == io.EOF { + break + } + if err != nil { + log.Fatal(err) + } + fmt.Println(record) + } +} +``` + +第一步是打开 `employees.csv` 文件。 + +我们使用的是标准库 `os` 。与往常一样,我们检查错误,如果有任何错误会返回它们(返回前,会先将错误信息打印)。 + +[@_@]: # (原文 we check for errors and return if they are some(but before returning, we are printing an error message) + +之后,我们使用 `csv` 包。我们用 `r:= csv.NewReader(file)` 创建一个 reader ,它将允许我们逐行读取文件。我们初始化一个行计数器来跟踪行号。 + +然后用 `for` 循环开始读取。我们用`record, err:= r.Read()` 读取新的一行。`record`变量是字符串(`[]string`)的一个 slice。接下来,我们检查错误,一点细微的差别是,当读至文件末尾结束,`r.Read()` 会使用 `io.EOF` 填充`err`。我们必须在检查`err != nil`之前先检查这个。如果我们已经到达文件的末尾,我们将用关键字 `break` 停止 `for` 循环。之后,我们就可以读取文件的数据了。 + +变量`record`将返回,例如`[V45657 John Ollivero M CEO]`。 + +数据存储在一个 slice 中,在索引0处,我们将找到`employeeID`,在索引1处是`name`,在索引2处是`genre`,在索引3处是 `position`! + +我们还必须定义employee类型: +```go +type employee struct { + name string + genre string + position string +} +``` +准备工作已经完成,让我们跳转到 map 的创建和使用部分。 + +## 13 初始化以及添加一个 key-element 对 +```go +// initialize and allocate a new map +employees := make(map[string]employee) +// ... +employee := employee{ + name: record[1], + genre: record[2], + position: record[3]} +// Add a new entry to the map +employees[employeeId] = employee +``` + +要添加一个键和一个元素组成的对,只需使用下面的语法: +```go +m[key]=value +``` + +## 14 检索一个值 + +要从 map 中获取元素,你必须知道它对应的 key。有两种不同的方法来实现这个操作: + +### 14.0.1 短语法 +假设你正在寻找与雇员 3 相关的数据。 + +你可以通过调用以下语句来检索值 (struct employee): +```go +walter := employees["V6555"] +``` + +这里,我们将 map `employeeMap`中包含的值赋给变量walter,键 (key) 为V6555。 + +### 14.0.2 如果 key 不存在怎么办? +但是如果这个值不存在呢?你会让你的程序陷入 panic 吗?让我们试试看: +```go +// when there is no such pair +ghost := employees["ABC55555"] +fmt.Println(ghost) +//{ } +fmt.Println(reflect.TypeOf(ghost)) +// main.employee +``` +在这里,我们试图获取id为 "ABC55555" 的员工的值。 + +该键在 map 上不存在。**Go将返回该类型的空值。** + +#### 14.0.2.1 警告!要非常小心这种语法,因为它可能会导致错误。 +在我们的 HR 软件示例中,假设在将数据加载到 map 中之后,你向用户提供某种类型的接口,在那里他们可以通过 id 查看员工的数据。如果用户输入 id "100" 会怎样?对于这些不存在的键,map 会返回一个空对象 `employee`。 + +我们可以猜测这个员工不存在,但不是100%肯定,因为这些空字段也可能来自已经损坏的文件。 + +这就是为什么 Go 的创造者们提供了一种更聪明的方法来检索 map 上的条目。 + +### 14.0.3 双值赋值 +替代语法如下: +```go +v, ok := myMap[k] +``` +变量`ok`是一个布尔值,用于指示 map 中 键-值对 的存在: + +- 键值对存在于 map 中,**v** 被赋值为键 **k** 处的值。 + +- 键值对不存在,**v** 被赋值为值类型的空值。 + +你经常会看到这样的写法: +```go +// lookup with two values assignment +employeeABC2, ok := employees["ABC2"] +if ok { + // the key-element pair exists in the map + fmt.Println(employeeABC2) +} else { + fmt.Printf("No employee with ID 'ABC2'") +} +``` + +如果你只是想测试一个键是否存在于 map 中,那么可以忽略这个值: +```go +// ignore the value retrieved +_, ok := employees["ABC3"] +if ok { + // the key-element pair exists in the map +} else { + fmt.Printf("No employee with ID 'ABC3'") +} +``` +在前面的例子中,我们通过使用下划线 (_) 来告诉编译器,这里我们不需要检索到的值。 + +有一种更短的方法来实现相同的操作: + +```go +// shorter code +if _, ok := employees["ABC4"]; ok { + // the key-element pair exists in the map +} else { + fmt.Println("No employee with ID 'ABC4'") +} +``` +两个值的赋值和 ok 值的检查在一行代码中完成了! + +### 14.0.4 警告!map 值是不可寻址的 +从 map 中检索的值是不可寻址的。不能打印 map 值的内存地址。 + +例如,以下代码: +```go +fmt.Printf("address of the100 %p", &employeeMap[100]) +``` +将导致编译错误: +```go +./main.go:66:14: cannot take the address of employeeMap[100] +``` +为什么会这样呢?因为 Go 会在添加新键值对时改变键值对的内存位置。Go将在内部执行此操作,以保持检索键-值对的复杂度维持在常数级别。因此,地址可能会失效。Go倾向于禁止访问一个可能无效的地址,而不是让你尝试。这是一件好事! + +### 14.0.5 内存使用情况的考虑 + +请注意,当你保存从 map 中提取的值时(如果你不再使用该 map ),Go仍然会在内存中保存整个 map 。垃圾收集器不会删除这些不再使用的内存。 + + +## 15 删除条目 + +可以使用内置的 delete 函数删除键值对。该函数有以下函数声明: +```go +func delete(m map[Type]Type1, key Type) +``` +调用 delete 需要: + +- 一个 map 作为第一个参数 +- 一个 key + +第二个参数是你想删除的条目的 **key**。 + +- 即使该条目在 map 中不存在,它也不会陷入 panic (并且能够编译)。 +- 如果第二个参数使用与 key 类型不同的类型,则程序将无法编译。 + +举个例子: + +如果你想从map `employees` 中删除索引为 2 的条目,你可以使用以下代码: +```go +delete(employees, "ABC4") +``` + +key 为 "ABC4" 的条目如果存在,将从 map 中被删除。 + +## 16 Length +你可以使用内置函数 **len** 来检索 map 中的条目数: +```go +fmt.Println(len(employees)) +// 3 +// There are three entries into the map + +// remove entry with index 2 +delete(employees, "V6555") + +fmt.Println(len(employeeMap)) +// 2 +// There are two entries into the map +``` + +## 17 遍历 map +你可以使用 for 循环和 range 子句来遍历 map 所有条目: +```go +for k, v := range employeeMap { + fmt.Printf("Key: %s - Value: %s\n", k, v) +} +// Key: V6555 - Value: {Walter Van Der Bolstenberg M Sales Manager} +// Key: V45657 - Value: {John Ollivero M CEO} +// Key: V45658 - Value: {Frane Elindo F CTO} +``` + +### 17.0.1 不要依赖迭代顺序! +注意,此代码段返回的元素序列,并非按照插入顺序排序。 +这是因为 map 中的顺序是没有保证的,如果我们尝试第二次运行相同的代码,我们可能会得到以下结果: +```go +Key: V45657 - Value: {John Ollivero M CEO} +Key: V45658 - Value: {Frane Elindo F CTO} +Key: V6555 - Value: {Walter Van Der Bolstenberg M Sales Manager} +``` +请记住这一点,因为它可能是错误的来源。 + +### 17.0.2 排序问题的解决方案 +可以使用另一个变量存储插入时的顺序来解决排序的问题。如果顺序对你来说很重要,可以这样解决: +```go +order := []string{} +order = append(order, employeeID) +employeeMap[employeeID] = employee +``` +在这里,我们创建了一个 slice `order`。这个 slice 将按插入顺序将键存储下来。因此,每次向 map 添加条目时,我们都通过调用`order = append(order, employeeID)`向 slice 添加键。 + +这样,我们就可以按照插入的顺序获取条目: + +```go +for _, k := range order { + fmt.Printf("Key: %s - Value: %s\n", k, employees[k]) +} +``` + +我们遍历切片顺序以获得键,然后通过调用`employees[k]`来检索条目值,其中`k`表示 map `employees`的键。 + +## 18 二维 map (map 的 map) +在前面的示例中,我们希望使用结构 `enployeeID => enployeeData` 来存储数据。 + +键是`employeeId`,值是结构体类型 `employee`。但假设我们不想存储一个结构体,而是存储另一个 map : + +![Maps](./imgs/2d_map.20011164.png) +二维 map + +在下图中有两个 map 。第二个 map 的类型是 `map[string]string`。我们以键 "Name" 、 "Position" 和 "Genre" 的形式存储,值是对应的员工数据。第一个map的类型是`map[int]map[string]string`。这个类型的表示法有点令人困惑,但当你仔细观察它时,它是有意义的: + +![Maps](./imgs/map_inside_map.dfe70134.png) + +map 的值是另一个 map + +第二个 map 是内部 map 。它是第一个 map 的值。这种类型的每个条目都有一个整数键,值是一个`map[string]string`。 + +在我看来,二维 map 太复杂了。你可能最好使用以结构体作为值的 map 。 + +## 19 试一试 +### 19.1 问题 +1. 如何检查一个键/元素对是否在一个 map 中? +2. Go maps 内部是如何实现的? +3. 哪些类型是禁止作为 map 的 key 来使用的? +4. 为什么这些类型被禁止作为 map 的 key 使用? +5. 当你遍历 map 时,运行时将按你插入键和元素的顺序返回键和元素。对还是错? +6. 怎样从 map 中删除一个 key-value 对? +7. 如何获取 map 中的 key-value 对的数量? +8. 如何遍历一个 map? +9. 如果 map `M` 中并不存在键 `K`,`M[K]` 将返回什么? + +### 19.2 答案 +1. 如何检查一个键/元素对是否在一个 map 中? + 1. 假设你想要检查 map `rooms`中是否存在键为102的键/元素对,可以使用`room, ok = rooms[102]`。当 `ok` 为 true 时,这个键值对就存在。 + +2. Go maps 内部是如何实现的? + 1. 在底层,Go maps 是由哈希表实现的 + +3. 哪些类型是禁止作为 map 的 key 来使用的? + 1. functions, slices, maps + 2. 任何由上述类型组成的数组 + 3. 任何包含上述类型的结构体 + +4. 为什么这些类型被禁止作为 map 的 key 使用? + 1. 因为比较操作符 == 和 != 并没有为这些类型完全定义。Go 需要能够在其内部实现中比较键。 + +5. 当你遍历 map 时,运行时将按你插入键和元素的顺序返回键和元素。对还是错? + 1. 错的,map 是一个无序的集合。Go 不保留插入顺序的记忆。如果你需要它,你必须自己保存它。 + +6. 怎样从 map 中删除一个 key-value 对? + 1. `delete(employees, "ABC4")`,当没有找到对应的 key 时,什么也不会发生。 + +7. 如何获取 map 中的 key-value 对的数量? + 1. `len(myMap)` + +8. 如何遍历一个 map? + 1. 使用一个 for 循环: `for k, v := range employees` + +9. 如果 map `M` 中并不存在键 `K`,`M[K]` 将返回什么? + 1. 它将返回元素类型的 0 值 + 2. 例如,如果元素是 int ,则返回 0 。 + +## 20 主要收获 +- map 是类型 V 的元素(值)的**无序**集合,由类型 K 的唯一键进行索引 +- map 类型这样表示:`map[K]V` +- map 中的元素称为 map 条目或键-值对。 +- 要初始化 map ,可以使用以下语法: + ```go + m := make(map[string]uint8) + + m := map[string]uint8{ "This is the key":42} + ``` +- map 类型的 0 值为 nil。 + + ```go + var m map[string]uint8 + log.Println(m) + ``` + - 这里将输出 `nil`。 + +- 要将元素插入到 map 中,可以使用以下语法:`m2[ "myNewKey"] = "the value"`。 +- **重要**:map 在使用前需要被初始化 + + - 下面的程序会导致 panic: + ```go + var m map[string]uint8 + m["test"] = 122 + + panic: assignment to entry in nil map + ``` + +- 要检索 map 中的元素,可以使用以下语法: + ```go + m := make(map[string]uint8)// fill the map + valueRetrieved := m[ "myNewKey"] + ``` + - 当没有找到值时,变量`valueretrived`将等于 map 值类型的 0 值。 + + - 在这里`valueretriserved`等于 0 ( uint8 类型的 0 值) + ```go + m := make(map[string]uint8) + // fill the map + valueRetrieved, ok := m[ "myNewKey"] + if ok { + // found an entry in the map with the key "myNewKey" + + } else { + // not found :( + + } + ``` + `ok`是一个布尔值,如果 map 中存在一个具有该键的条目,它就等于true + +- 可以使用 for 循环遍历 map (使用 range 子句) + + - 警告:不应该使用插入顺序(它不保证)! + - 为了在内存中保持 map 中的插入顺序,可以创建一个 slice 并将每个键添加到其中 + - 然后可以遍历 slice 并按照插入的顺序获取每个值。 + +- 在 map 中插入和查找非常快,即使 map 里有很多条目。 + +--- +1. https://golang.org/ref/spec#Map_types +2. Go Specs https://golang.org/ref/spec#Map_types +3. https://en.wikipedia.org/wiki/Computational_complexity +4. https://en.wikipedia.org/wiki/Time_complexity +5. You can find more info about this notation on this Wikipedia article: https://en.wikipedia.org/wiki/Big_O_notation + +## Bibliography +- [stevens2006fast] Stevens, Marc. 2006. “Fast Collision Attack on Md5.” IACR Cryptology ePrint Archive 2006: 104. \ No newline at end of file diff --git a/chap-22-Maps/imgs/2d_map.20011164.png b/chap-22-Maps/imgs/2d_map.20011164.png new file mode 100644 index 0000000000000000000000000000000000000000..417dfc70499e739f6c580110e5039611d9d40d77 Binary files /dev/null and b/chap-22-Maps/imgs/2d_map.20011164.png differ diff --git a/chap-22-Maps/imgs/go_hashmap.c706cf7b.png b/chap-22-Maps/imgs/go_hashmap.c706cf7b.png new file mode 100644 index 0000000000000000000000000000000000000000..f6dfe572bff9eacd40645d896fc7870eb027bbbb Binary files /dev/null and b/chap-22-Maps/imgs/go_hashmap.c706cf7b.png differ diff --git a/chap-22-Maps/imgs/hash_table.3a049d36.png b/chap-22-Maps/imgs/hash_table.3a049d36.png new file mode 100644 index 0000000000000000000000000000000000000000..f9004799afd0ff13ebcaed82caabea5ae93b201d Binary files /dev/null and b/chap-22-Maps/imgs/hash_table.3a049d36.png differ diff --git a/chap-22-Maps/imgs/map_definition.0dfd1bb3.png b/chap-22-Maps/imgs/map_definition.0dfd1bb3.png new file mode 100644 index 0000000000000000000000000000000000000000..079ab8f935df04a40fbf63f9cd4b9313aec47893 Binary files /dev/null and b/chap-22-Maps/imgs/map_definition.0dfd1bb3.png differ diff --git a/chap-22-Maps/imgs/map_inside_map.dfe70134.png b/chap-22-Maps/imgs/map_inside_map.dfe70134.png new file mode 100644 index 0000000000000000000000000000000000000000..347ed2eb743df212b3fdf9865cb50fcde670ba34 Binary files /dev/null and b/chap-22-Maps/imgs/map_inside_map.dfe70134.png differ diff --git a/chap-22-Maps/imgs/maps.a1e5819d.jpg b/chap-22-Maps/imgs/maps.a1e5819d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..21077f7f403d03f71aecb98744a6d4be2e34b22f Binary files /dev/null and b/chap-22-Maps/imgs/maps.a1e5819d.jpg differ