Go语言Map
Map概念
map 是一种特殊的数据结构:一种键值(pair)对的无序集合,map 的一个元素是 key,对应的另一个元素是 value,所以这个结构也称为关联数组或字典。这是一种快速寻找值的理想结构:给定 key,可以迅速定位到对应的 value。
map 这种数据结构在其他编程语言中也称为字典(Python)、hash 和 HashTable 等。
声明与初始化
map是引用类型,可以使用如下声明:
var map1 map[keytype]valuetypevar 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 mainimport "fmt"func main() {var mapLit map[string]int//var mapCreated map[string]float32var mapAssigned map[string]intmapLit = map[string]int{"one": 1, "two": 2}mapCreated := make(map[string]float32)mapAssigned = mapLitmapCreated["key1"] = 4.5mapCreated["key2"] = 3.14159mapAssigned["two"] = 3fmt.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: 1Map created at "key2" is: 3.141590Map assigned at "two" is: 3Mpa 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 mainimport "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 mainimport ("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 := 0for k, _ := range barVal {keys[i] = ki++}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 stringvalue int}