Go语言Map

Map概念

map 是一种特殊的数据结构:一种键值(pair)对的无序集合,map 的一个元素是 key,对应的另一个元素是 value,所以这个结构也称为关联数组或字典。这是一种快速寻找值的理想结构:给定 key,可以迅速定位到对应的 value

map 这种数据结构在其他编程语言中也称为字典(Python)hashHashTable 等。

声明与初始化

map是引用类型,可以使用如下声明:

  1. var map1 map[keytype]valuetype
  2. var map1 map[string]int
  3. // [keytype] 和 valuetype 之间允许有空格,但是 gofmt 移除了空格

在声明的时候不需要知道 map 的长度,map 可以动态扩容的。map 的默认值是 nil

key 可以是任意可以用 == 或者 != 操作符比较的类型,比如 stringintfloat。所以数组、切片和结构体不能作为 key,但是指针和接口类型可以。如果要用结构体作为 key 可以提供 Key()Hash() 方法,这样可以通过结构体的域计算出唯一的数字或者字符串的 key

value 可以是任意类型的,通过使用空接口类型,我们可以存储任意值,但是使用这种类型作为值时需要先做一次类型断言。

map 传递给函数的消耗很小,在 32 位机器上占 4 个字节,64 位机器上占 8 个字节,无论实际上存储了多少数据。通过 keymap 中寻找值都是很快的,比线性查找快得多,但是仍然比从数组和切片的索引中直接读取要慢 100 倍,所以如果你很在乎性能的话还是建议用切片来解决问题。

map 也可以用函数作为自己的值,这样就可以用来做分支结构,key 用来选择要执行的函数。

如果 key1map1key,那么map1[key1] 就是对应 key1 的值,就如同数组索引符号一样(数组可以视为一种简单形式的 map,key 是从 0 开始的整数)。

key1 对应的值可以通过赋值符号来设置为 val1:map1[key1] = val1

v := map1[key1] 可以将 key1 对应的值赋值给 v。如果 map 中没有 key1 的存在,那么 v 将被赋值为 map1 的值类型的空值。

常用的 len(map1) 方法可以获得 map 中的键值对数目,这个数目是可以伸缩的,因为 map-pairs 在运行时可以动态添加和删除。

  1. package main
  2. import "fmt"
  3. func main() {
  4. var mapLit map[string]int
  5. //var mapCreated map[string]float32
  6. var mapAssigned map[string]int
  7. mapLit = map[string]int{"one": 1, "two": 2}
  8. mapCreated := make(map[string]float32)
  9. mapAssigned = mapLit
  10. mapCreated["key1"] = 4.5
  11. mapCreated["key2"] = 3.14159
  12. mapAssigned["two"] = 3
  13. fmt.Printf("Map literal at \"one\" is: %d\n", mapLit["one"])
  14. fmt.Printf("Map created at \"key2\" is: %f\n", mapCreated["key2"])
  15. fmt.Printf("Map assigned at \"two\" is: %d\n", mapLit["two"])
  16. fmt.Printf("Map literal at \"ten\" is: %d\n", mapLit["ten"])
  17. }

输出结果:

  1. Map literal at "one" is: 1
  2. Map created at "key2" is: 3.141590
  3. Map assigned at "two" is: 3
  4. Mpa literal at "ten" is: 0

mapLit 说明了 map literals 的使用方法,map 可以用 {key1: val1, key2: val2} 的描述方法来初始化,就像数组和结构体一样。

map 是 引用类型,内存可以使用 make 方法来分配。

map 的初始化:var map1 = make(map[keytype]valuetype)

也可以简写为:map1 := make(map[keytype]valuetype)

注意:不要使用 new 构造 map,永远记得用 make 来构造

如果你错误的使用 new() 分配了一个引用对象,你会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址:mapCreated := new(map[string]float32)。接下来当我们调用:mapCreated["key1"] = 4.5 的时候,编译器会报错: invalid operation: mapCreated[“key1”] (index of type *map[string]float32).

为了说明值可以是任意类型的,这里给出了一个使用 func() int 作为值的 map:

  1. package main
  2. import "fmt"
  3. func main() {
  4. mf := map[int]func() int{
  5. 1: func() int { return 10 },
  6. 2: func() int { return 20 },
  7. 5: func() int { return 50 },
  8. }
  9. fmt.Println(mf)
  10. }

输出结果为:map[1:0x10903be0 5:0x10903ba0 2:0x10903bc0],整型都被映射到了函数地址。

map 容量

和数组不同,map 可以根据新增的 key-value 动态的伸缩,因此它不存在固定长度或者最大限制。但是你也可以选择标明 map 的初始容量 capacity,就像这样:make(map[keytype]valuetype, cap)。例如:

  1. map2 := make(map[string]float32, 100)

map 增长到容量上限的时候,如果再增加新的 key-valuemap 的大小会自动加 1。所以出于性能的考虑,对于大的 map 或者会快速扩张的 map,最好先指定最大的容量。

用切片作为 map 的值

既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?

例如:当我们要处理 unix 机器上的所有进程,以父进程(pid 为整型)作为 key,所有的子进程(以所有子进程的 pid 组成的切片)作为 value。通过将 value 定义为 []int 类型或者其他类型的切片,就可以优雅的解决这个问题。

  1. mp1 := make(map[int][]int)
  2. mp2 := make(map[int]*[]int)

判断键值是否存在及删除元素

判断 map1 中是否存在 key1。在上面的例子中,我们已经见过可以使用 val1 = map1[key1] 的方法获取 key1 对应的值 val1。如果 map 中不存在 key1val1 就是一个值类型的空值。

这样我们就没法区分到底是 key1 不存在,还是它对应的 value 就是空值。

为了解决这个问题,我们可以这么用:val1, isPresent = map1[key1]

isPresent 返回一个 bool 值,如果 key1 存在于 map1val1 就是 key1 对应的 value 值,并且 isPresenttrue;如果 key1 不存在,val1 就是一个空值,并且 isPresent 会返回 false

如果你只是想判断某个 key 是否存在而不关心它对应的值到底是多少,你可以这么做:

  1. _, ok := map1[key1]
  2. // 如果key1存在则ok == true,否则ok为false

map1 中删除 key1,直接 delete(map1, key1) 就可以。如果 key1 不存在,该操作不会产生错误。

map排序

map 默认是无序的,不管是按照 key 还是按照 value 默认都不排序。

如果你想为 map 排序,需要将 key(或者 value)拷贝到一个切片,再对切片排序(使用 sort 包),然后可以使用切片的 for-range 方法打印出所有的 keyvalue

  1. // the telephone alphabet:
  2. package main
  3. import (
  4. "fmt"
  5. "sort"
  6. )
  7. var (
  8. barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23,
  9. "delta": 87, "echo": 56, "foxtrot": 12,
  10. "golf": 34, "hotel": 16, "indio": 87,
  11. "juliet": 65, "kili": 43, "lima": 98}
  12. )
  13. func main() {
  14. fmt.Println("unsorted:")
  15. for k, v := range barVal {
  16. fmt.Printf("Key: %v, Value: %v / ", k, v)
  17. }
  18. keys := make([]string, len(barVal))
  19. i := 0
  20. for k, _ := range barVal {
  21. keys[i] = k
  22. i++
  23. }
  24. sort.Strings(keys)
  25. fmt.Println()
  26. fmt.Println("sorted:")
  27. for _, k := range keys {
  28. fmt.Printf("Key: %v, Value: %v / ", k, barVal[k])
  29. }
  30. }

输出结果:

  1. unsorted:
  2. Key: bravo, Value: 56 / Key: echo, Value: 56 / Key: indio, Value: 87 / Key: juliet, Value: 65 / Key: alpha, Value: 34 / Key: charlie, Value: 23 / Key: delta, Value: 87 / Key: foxtrot, Value: 12 / Key: golf, Value: 34 / Key: hotel, Value: 16 / Key: kili, Value: 43 / Key: lima, Value: 98 /
  3. sorted:
  4. Key: alpha, Value: 34 / Key: bravo, Value: 56 / Key: charlie, Value: 23 / Key: delta, Value: 87 / Key: echo, Value: 56 / Key: foxtrot, Value: 12 / Key: golf, Value: 34 / Key: hotel, Value: 16 / Key: indio, Value: 87 / Key: juliet, Value: 65 / Key: kili, Value: 43 / Key: lima, Value: 98 /

提示:如果你想要一个排序的列表你最好使用结构体切片,这样会更有效:

  1. type name struct {
  2. key string
  3. value int
  4. }