创建
Map
是一个无序的 key/value
对的集合,其中 key
是唯一的。如果我们在向字典中放入一个键值对的时候其中已经有相同的键的话,那么与此键关联的那个值会被新值替换。
map
的字面量是 map[K]T
,其中
K
:键的类型T
:元素(或称值)的类型
注意:键类型必须是支持 ==
比较运算符的数据类型,否则会引起错误。即不能是数组、切片、字典、结构体、函数类型,但是指针和接口类型可以。对于值的类型,没有限制。
map
可以通过测试 key
是否相等来判断是否已经存在。虽然浮点数类型也是支持相等运算符比较的,但最好别使用将浮点数作为 key
,可能出现 NaN
和任何浮点数都不相等。
含有数组切片的结构体不能作为
key
,只包含内建类型的struct
是可以作为key
的。如果要用结构体作为key
可以提供Key()
和Hash()
方法,这样可以通过结构体的域计算出唯一的数字或者字符串的key
。
示例:
var m1 map[string]int
m1 = map[string]int{"a": 1, "b": 2}
var m2 map[string]int = map[string]int{}
m2["a"] = 3
m2["b"] = 4
m3 := map[string]int{"a": 1, "b": 2}
m4 := map[string]int{}
m4["a"] = 1
m4["b"] = 2
复制代码
与切片类型相同,字典类型属于引用类型。它的零值即为 nil
。(详见零值问题)
map
可以根据新增的 key-value
对动态的伸缩,不存在固定长度或者最大限制。但是你可以选择标明 map
的初始容量 capacity
:
m1 := make(map[string]int)
m1["a"] = 1
m1["b"] =2
m2 := make(map[string]int, 100)
复制代码
不要使用
new
,永远用make
来构造map
。如果你错误的使用new()
分配了一个引用对象,你会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址。
取值
对于 map
来说,如果获取不存在的 key,就以它的值类型的空值(或称默认值)作为值结果。那么,我们在不知道 map 确切的情况下,怎么知道要取的 key 是否存在呢?
m1 := map[string]int{"a": 1, "b": 2}
x := m1["a"] // 1
x, ok := m1["a"] // 1 true _, ok := m1["a"] 可以用来判断 key 是否存在。
y := m1["c"] // 0
y, ok := m1["c"] // 0 false
复制代码
map
中的元素并不是一个变量,因此我们不能对 map
的元素进行取址操作。禁止对 map
元素取址的原因是 map
可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效:
&m1 // 没问题
&m1["a"] // cannot take the address of m1["a"]
复制代码
修改
m1 := map[string]int{"a": 1, "b": 2}
m1["a"] = 3
fmt.Println(m1) // map[a:3 b:2]
m1["c"] = 4
fmt.Println(m1) // map[c:4 a:3 b:2]
复制代码
另外,x += y
和 x++
等简短赋值语法也可以用在 map
。
删除
delete
操作是安全的,有则删除,无则不做。
m1 := map[string]int{"a": 1, "b": 2}
delete(m1, "a") // 有则删除
delete(m1, "c") // 无则不做
复制代码
遍历
map
的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。在实践中,遍历的顺序是随机的,每一次遍历的顺序都不相同。这是故意的,每次都使用随机的遍历顺序可以强制要求程序不会依赖具体的哈希函数实现。
for key, value := range map1 {
...
}
// 如果只想获取 key,你可以这么使用:
for key := range map1 {
fmt.Printf("key is: %d\n", key)
}
复制代码
排序
map
是无序的,如果你想按顺序遍历 map
,需要将 key
(或者 value
)拷贝到一个切片,再对切片排序(使用 sort
包):
package main
import (
"fmt"
"sort"
)
var m = map[string]int{
"d": 4,
"a": 1,
"b": 2,
"c": 3}
func main() {
// 因为我们一开始就知道names的最终大小,因此给slice分配一个合适的大小将会更有效
// keys := []string{}
keys := make([]string, 0, len(m))
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
fmt.Println(key, m[key])
}
}
复制代码
但是如果你想要一个排序的列表,最好使用结构体切片,这样会更有效:
type name struct {
key string
value int
}
复制代码
切片
切片的元素是 map
类型:
package main
import "fmt"
func main() {
mySlice := make([]map[string]int, 3) // 分配切片
mySlice[0] = make(map[string]int, 2) // 分配切片中的每个map元素
mySlice[0]["a"] = 1
mySlice[0]["b"] = 2
mySlice[0]["c"] = 3 // map类型自动扩展
fmt.Println(mySlice) // [map[a:1 b:2 c:3] map[] map[]]
for i := range mySlice {
mySlice[i] = make(map[string]int, 3)
mySlice[i]["a"] = 10
mySlice[i]["b"] = 20
mySlice[i]["c"] = 30
}
fmt.Println(mySlice) // [map[a:10 b:20 c:30] map[a:10 b:20 c:30] map[a:10 b:20 c:30]]
}
复制代码
当然,map
的 value
也可以是切片类型:
var m1 map[string][]int
复制代码
比较
和 slice
一样,map
之间也不能进行相等比较,唯一的例外是和 nil
进行比较。要判断两个 map
是否包含相同的 key
和 value
,我们必须通过一个循环实现:
func equal(x, y map[string]int) bool {
if len(x) != len(y) {
return false
}
for k, xv := range x {
// 我们不能简单地用xv != y[k]判断
if yv, ok := y[k]; !ok || yv != xv {
return false
}
}
return true
}
复制代码
零值问题
map
类型的零值是 nil
,也就是没有引用任何哈希表。map
上的大部分操作,包括查找、删除、len
和 range
循环都可以安全工作在 nil
值的 map
上,它们的行为和一个空的 map
类似。但是向一个 nil
值的 map
存入元素将导致一个 panic
异常。在向 map
存数据前必须先创建 map
。
var m1 map[string]int // nil
m2 := map[string]int{} // 空map
fmt.Println(m1 == nil) // true 0
fmt.Println(m2 == nil) // false 0
m1["a"] = 1 // panic: assignment to entry in nil map
m2["a"] = 1 // 正常
复制代码
实现Set
Go
语言中并没有 set
类型,由于 map
中的 key
也是不相同的,可以将忽略 value
的 map
当成 set
。