本文转载于 SegmentFault 社区
作者:lioney
No.1
定义
• Go语言中映射是一种字典类型的数据结构,类似于 c++ 和 java 中的 hashmap,用于存储一系列无序的键值对。 • 映射是基于键来存储值。映射的优势是能够基于键快速索引数据。键就像索引一样,指向与该键关联的值,在内存中键值对的关系如下图所示。![aa4ebf89487fcb417a0816a12f862602.png](https://i-blog.csdnimg.cn/blog_migrate/4708f5e063502ac48a3b89ccdc44cf56.png)
No.2
内部实现
• 映射是一个集合,可以使用类似处理数组和切片的方式迭代映射中的元素。但映射是无序的,不能对键值对进行排序。即使按顺序保存,每次迭代的时候顺序也不一样。无序的原因是映射的实现使用了散列表。 • 这个散列表是由 hmap 实现的,hmap 中维护着存储键值对的 bucket 数组,hmap 和 bucket 数组的关系如下图所示。![f88d584de118928e7932829a11f7aa30.png](https://i-blog.csdnimg.cn/blog_migrate/a812996e644ce41cc3f077a37ef0f6bb.png)
![f27a01b7c8590573e1f4c100b1126ef5.png](https://i-blog.csdnimg.cn/blog_migrate/778963cf709be00290ed9683f42959d5.png)
![fae4a3dfe2722adf8489b312c00ad677.png](https://i-blog.csdnimg.cn/blog_migrate/411ce49cdd65ebc1e9402ab41a8d8d98.png)
![387752145d63917e88da2d60a37ba34d.png](https://i-blog.csdnimg.cn/blog_migrate/958b292d40979786c0d6e49010a79dfd.jpeg)
No.3
映射的创建
(1) 使用字面量创建
// 创建一个键为字符串类型,值为整形的map
m1 := map[string]int{"last":2019, "now":2020}
// 获取map中的元素
fmt.Println(m1["last"]) // 2019
fmt.Println(m1["now"]) // 2020
// 使用字面量创建一个空map
m2 := map[string]string{}
fmt.Println(m2) // map[]
映射的键的类型可以是内置类型,也可以是结构类型,但这个类型必须可以使用==运算符做比较。切片,函数以及包含切片的结构类型由于具有引用语义,不能作为映射的键,否则会造成编译错误。
(2) 使用内置的 make 函数来创建
m1 := make(map[int] string) // map的容量使用默认值
m1[1] = "lioney"
m2 := make(map[int] string, 10) // map的容量使用给定值
m2[1] = "carlos"
fmt.Println(m1[1]) // lioney
fmt.Println(m2[1]) // carlos
No.4
映射支持的操作
• map 中单个键值的访问格式为 mapName[key],可以用于获取或更新 map 中的元素 • 可以使用 for range 遍历 map 中的元素,不保证每次迭代顺序一致 • 删除 map 中的某个键值对,使用语法 delete(mapName, key) • 使用内置函数 len() 获取 map 中保存的键值对数量,map 中不支持 cap() 函数
package main
import "fmt"
func main() {
// 创建一个空的map
m1 := make(map[int] string)
// 向map中添加元素
m1[1] = "lioney"
m1[2] = "carlos"
m1[3] = "tom"
// 从map中删除键为3的元素
delete(m1, 3)
// len()表示map中键值对的数量
fmt.Println("len=", len(m1))
// 遍历map
for k, v := range m1 {
fmt.Println("key=", k, "value=", v)
}
}
上述代码编译后运行结果如下:
len= 2
key= 2 value= carlos
key= 1 value= lioney
Process finished with exit code 0
No.5
映射的使用要点
(1) 对 nil 映射赋值会产生运行时错误
和切片类似,映射在使用时必须对其底层数组进行初始化,要么使用 make 进行初始化,要么使用字面量初始化,如果只是简单地声明了一个 map,而没有进行初始化,就是 nil 映射,是不能对其赋值的,请看下面代码:
// 声明一个map
var colors map[string]string
// 将red加入colors
colors["red"] = "#da137" // panic: assignment to entry in nil map
可以做如下修改
// 声明一个map
var colors map[string]string
// 对map进行初始化
colors = make(map[string]string)
// 将red加入colors
colors["red"] = "#da137" // no panic or error
也可以做如下修改:
// 使用字面量创建要给空map
colors := map[string]string{}
// 将red加入colors
colors["red"] = "#da137" // no panic or error
强烈推荐使用第二种,因为用字面量创建 map 比较简洁而且比较快
(2) 从映射获取值并判断键是否存在
在 Go 语言里,通过键来索引值时,即便这个键不存在也会返回一个值,有时候我们需要判断获取到的值是否时默认的零值,代码如下所示。
// 使用字面量创建一个空map
colors := map[string]string{}
// 将red加入colors
colors["red"] = "#da137"
// 获取blue对应的值并判断是否为零值
value1, exists := colors["blue"]
if exists {
fmt.Println(value1)
}
// 也可以通过值直接判断是否为零值
value2 := colors["blue"]
if value2 != "" {
fmt.Println(value2)
}
(3) 在函数间传递映射
package main
import (
"fmt"
"unsafe"
)
func foo(m map[string]string) {
// 打印参数的大小
fmt.Println("参数大小", unsafe.Sizeof(m))
// 删除键为green的元素
delete(m, "green")
}
func main() {
// 使用字面量创建一个空map
colors := map[string]string{}
// 将red加入colors
colors["red"] = "#da137"
colors["coral"] = "#ff7f50"
colors["green"] = "#228b22"
// 调用foo函数
foo(colors)
// 遍历打印map
for k, v := range colors {
fmt.Println("key=", k, "value=", v)
}
}
编译并运行以上代码,结果如下:
参数大小 8
key= red value= #da137
key= coral value= #ff7f50
Process finished with exit code 0
映射是引用类型的数据结构,在函数间传递映射的时候,不会拷贝映射底层的数据,从上面代码的运行结果可以看出,只传递了一个 8 字节大小的指针,实际上,map 类型就是一个指针类型,函数对映射的操作都是通过这个指针间接进行的,因此对映射中数据的修改,其它引用到该映射的地方也能感知到。
(4) 修改映射中的结构体类型,必须整体赋值(!!!)
package main
import "fmt"
type User struct {
name string
age int
}
func main() {
m := make(map[int]User)
user := User{
name: "lioney",
age: 18,
}
// 将user加入到map中
m[1] = user
// 修改user
// 不能通过map引用直接修改!!!
//m[1].age = 2 // error: cannot assign to struct field m[1].age in map
// 必须整体替换
user.age = 2
m[1] = user
fmt.Println(m)
}
(5) 并发问题
Go 语言内置的 map 不是并发安全的,若是在并发场景下以共享的形式访问 map 中的元素,必须加锁,要想使用并发安全的 map,可以使用 Go 语言标准库中 sync 包下提供的 map。参考文献:
Ryu_Gou:解剖Go语言map底层实现 William Kennedy 等《Go 语言实战》第四章相关内容 李文塔《Go 语言核心编程》第一章相关内容
- END -
![90625bb79740515a86862edeb2336c20.png](https://i-blog.csdnimg.cn/blog_migrate/ab44104adc19fe6670a237080edd08d9.jpeg)