Go语言Map
Map概念
map
是一种特殊的数据结构:一种键值(pair)对的无序集合,map
的一个元素是 key
,对应的另一个元素是 value
,所以这个结构也称为关联数组或字典。这是一种快速寻找值的理想结构:给定 key
,可以迅速定位到对应的 value
。
map
这种数据结构在其他编程语言中也称为字典(Python)
、hash
和 HashTable
等。
声明与初始化
map
是引用类型,可以使用如下声明:
var map1 map[keytype]valuetype
var map1 map[string]int
// [keytype] 和 valuetype 之间允许有空格,但是 gofmt 移除了空格
在声明的时候不需要知道 map
的长度,map
可以动态扩容的。map
的默认值是 nil
。
key
可以是任意可以用 ==
或者 !=
操作符比较的类型,比如 string
、int
、float
。所以数组、切片和结构体不能作为 key
,但是指针和接口类型可以。如果要用结构体作为 key
可以提供 Key()
和 Hash()
方法,这样可以通过结构体的域计算出唯一的数字或者字符串的 key
。
value
可以是任意类型的,通过使用空接口类型,我们可以存储任意值,但是使用这种类型作为值时需要先做一次类型断言。
map
传递给函数的消耗很小,在 32
位机器上占 4
个字节,64
位机器上占 8
个字节,无论实际上存储了多少数据。通过 key
在 map
中寻找值都是很快的,比线性查找快得多,但是仍然比从数组和切片的索引中直接读取要慢 100
倍,所以如果你很在乎性能的话还是建议用切片来解决问题。
map
也可以用函数作为自己的值,这样就可以用来做分支结构,key
用来选择要执行的函数。
如果 key1
是 map1
的 key
,那么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
在运行时可以动态添加和删除。
package main
import "fmt"
func main() {
var mapLit map[string]int
//var mapCreated map[string]float32
var mapAssigned map[string]int
mapLit = map[string]int{"one": 1, "two": 2}
mapCreated := make(map[string]float32)
mapAssigned = mapLit
mapCreated["key1"] = 4.5
mapCreated["key2"] = 3.14159
mapAssigned["two"] = 3
fmt.Printf("Map literal at \"one\" is: %d\n", mapLit["one"])
fmt.Printf("Map created at \"key2\" is: %f\n", mapCreated["key2"])
fmt.Printf("Map assigned at \"two\" is: %d\n", mapLit["two"])
fmt.Printf("Map literal at \"ten\" is: %d\n", mapLit["ten"])
}
输出结果:
Map literal at "one" is: 1
Map created at "key2" is: 3.141590
Map assigned at "two" is: 3
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:
package main
import "fmt"
func main() {
mf := map[int]func() int{
1: func() int { return 10 },
2: func() int { return 20 },
5: func() int { return 50 },
}
fmt.Println(mf)
}
输出结果为:map[1:0x10903be0 5:0x10903ba0 2:0x10903bc0]
,整型都被映射到了函数地址。
map 容量
和数组不同,map
可以根据新增的 key-value
动态的伸缩,因此它不存在固定长度或者最大限制。但是你也可以选择标明 map
的初始容量 capacity
,就像这样:make(map[keytype]valuetype, cap)
。例如:
map2 := make(map[string]float32, 100)
当 map
增长到容量上限的时候,如果再增加新的 key-value
,map
的大小会自动加 1
。所以出于性能的考虑,对于大的 map
或者会快速扩张的 map
,最好先指定最大的容量。
用切片作为 map 的值
既然一个 key
只能对应一个 value
,而 value
又是一个原始类型,那么如果一个 key
要对应多个值怎么办?
例如:当我们要处理 unix
机器上的所有进程,以父进程(pid
为整型)作为 key
,所有的子进程(以所有子进程的 pid 组成的切片)作为 value
。通过将 value
定义为 []int
类型或者其他类型的切片,就可以优雅的解决这个问题。
mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)
判断键值是否存在及删除元素
判断 map1
中是否存在 key1
。在上面的例子中,我们已经见过可以使用 val1 = map1[key1]
的方法获取 key1
对应的值 val1
。如果 map
中不存在 key1
,val1
就是一个值类型的空值。
这样我们就没法区分到底是 key1
不存在,还是它对应的 value
就是空值。
为了解决这个问题,我们可以这么用:val1, isPresent = map1[key1]
isPresent
返回一个 bool
值,如果 key1
存在于 map1
,val1
就是 key1
对应的 value
值,并且 isPresent
为true
;如果 key1
不存在,val1
就是一个空值,并且 isPresent
会返回 false
。
如果你只是想判断某个 key
是否存在而不关心它对应的值到底是多少,你可以这么做:
_, ok := map1[key1]
// 如果key1存在则ok == true,否则ok为false
从 map1
中删除 key1
,直接 delete(map1, key1)
就可以。如果 key1
不存在,该操作不会产生错误。
map排序
map
默认是无序的,不管是按照 key
还是按照 value
默认都不排序。
如果你想为 map
排序,需要将 key
(或者 value
)拷贝到一个切片,再对切片排序(使用 sort
包),然后可以使用切片的 for-range
方法打印出所有的 key
和 value
。
// the telephone alphabet:
package main
import (
"fmt"
"sort"
)
var (
barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23,
"delta": 87, "echo": 56, "foxtrot": 12,
"golf": 34, "hotel": 16, "indio": 87,
"juliet": 65, "kili": 43, "lima": 98}
)
func main() {
fmt.Println("unsorted:")
for k, v := range barVal {
fmt.Printf("Key: %v, Value: %v / ", k, v)
}
keys := make([]string, len(barVal))
i := 0
for k, _ := range barVal {
keys[i] = k
i++
}
sort.Strings(keys)
fmt.Println()
fmt.Println("sorted:")
for _, k := range keys {
fmt.Printf("Key: %v, Value: %v / ", k, barVal[k])
}
}
输出结果:
unsorted:
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 /
sorted:
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 /
提示:如果你想要一个排序的列表你最好使用结构体切片,这样会更有效:
type name struct {
key string
value int
}