【读书笔记&个人心得】第8章:Map
Map
一种元素对 (pair) 的无序集合,给定 key,对应的 value 可以迅速定位,这个结构也称为关联数组或字典(如需要有序请使用结构体)
给定 key,对应的 value 可以迅速定位
声明、初始化和 make
声明
var map1 map[keytype]valuetype
var map1 map[string]int
key一般是string、int、float32
key 可以是任意可以用 == 或者 != 操作符比较的类型,比如 string、int、float32(64)
指针、接口和结构体作key
数组、切片和结构体不能作为 key ,但是指针和接口类型可以,如果要用结构体作为 key 可以提供 Key() 和 Hash() 方法,这样可以通过结构体的域计算出唯一的数字或者字符串的 key
(译者注:含有数组切片的结构体不能作为 key,只包含内建类型的 struct 是可以作为 key 的)
value可以是任意类型
value 可以是任意类型的;通过使用空接口类型(详见第 11.9 节),我们可以存储任意值,但是使用这种类型作为值时需要先做一次类型断言(详见第 11.3 节)
map 传递给函数的代价很小
map 传递给函数的代价很小:在 32 位机器上占 4 个字节,64 位机器上占 8 个字节,无论实际上存储了多少数据。通过 key 在 map 中寻找值是很快的,比线性查找快得多,但是仍然比从数组和切片的索引中直接读取要慢 100 倍;所以如果你很在乎性能的话还是建议用切片来解决问题
性能:数组/切片>map>线性表
函数作value
可以用来做分支结构(详见第 5 章):key 用来选择要执行的函数。实际上值存的是函数的地址
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]: 整型都被映射到函数地址
取值赋值
取值:v := map1[key1] (如果 map 中没有 key1 存在,那么 v 将被赋值为 map1 的值类型的空值)
赋值:map1[key1] = val1
map的长度
len(map1) 方法可以获得 map 中的 pair 数目,这个数目是可以伸缩的
map的实际使用
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"])
}
mapLit说明 map 可以用 {key1: val1, key2: val2} 的描述方法来初始化,就像数组和结构体一样。(字符数组用("string"))
map 是 引用类型 的: 内存用 make() 方法来分配(申请内存并初始化)
map 的初始化:
var map1 = make(map[keytype]valuetype)
或者简写为:
map1 := make(map[keytype]valuetype)
mapCreated := make(map[string]float32)
相当于:
mapCreated := map[string]float32{}
不要使用 new()
不要使用 new(),永远用 make() 来构造 map
【注意】 如果你错误地使用 new() 分配了一个引用对象,你会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址(指针没指向具体大小内存)
PS:联系第7章:new() 和 make() 的区别
mapCreated := new(map[string]float32)
mapCreated["key1"] = 4.5 // error:invalid operation: mapCreated["key1"] (index of type *map[string]float32).
map 容量:最好先标明
和数组不同,map 可以根据新增的 key-value 对动态的伸缩,因此它不存在固定长度或者最大限制。但是你也可以选择标明 map 的初始容量 capacity
map2 := make(map[string]float32, 100)
当 map 增长到容量上限的时候,如果再增加新的 key-value 对,map 的大小会自动加 1。所以出于性能的考虑,对于大的 map 或者会快速扩张的 map,即使只是大概知道容量,也最好先标明
初始化时直接输入可能的值
这也是一种先标明容量的方法
noteFrequency := map[string]float32 {
"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
"G0": 24.50, "A0": 27.50, "B0": 30.87, "A4": 440}
一个 key 要对应多个值
既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?
通过将 value 定义为 []int 类型或者其他类型的切片,就可以优雅地解决这个问题
mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)// 这个怎么使用??
当value为切片时,make是否会自动初始化?
并不会
package main
import "fmt"
func main() {
map1 := make(map[int][]int)
// map1[0][0] = 1 // runtime error: index out of range [0] with length 0
map1[0] = make([]int, 10)
map1[0][0] = 1
fmt.Printf("%d", map1[0][0])//1
}
测试键值对是否存在及删除元素
如果 map 中没有 key1 存在,那么 v 将被赋值为 map1 的值类型的空值,这导致现在我们没法区分到底是 key1 不存在还是它对应的 value 就是空值
为了解决这个问题,我们可以这么用:
val1, isPresent = map1[key1]// 存在时isPresent为true
只是想判断某个 key 是否存在
if _, ok := map1[key1]; ok {
// ...
}
map1 中删除 key1
delete(map1, key1) // key1 不存在,该操作不会产生错误
for-range 和map的配套用法
常规用法
for key, value := range map1 {
...
}
value是可选的
package main
import "fmt"
func main() {
map1 := map[int]int{0: 0, 1: 1, 2: 2, 3: 3}
for key, value := range map1 {
fmt.Printf("%d:%d ", key, value)
}
for key := range map1 {
fmt.Printf("%d:%d ", key, map1[key])
}
for _, value := range map1 {
fmt.Printf("%d ", value)
}
}
输出:
0:0 1:1 2:2 3:3 0:0 1:1 2:2 3:3 2 3 0 1
从结果可以看到,值 明显是无序的
注意 map 不是按照 key 的顺序排列的,也不是按照 value 的序排列的
译者注:map 的本质是散列表,而 map 的增长扩容会导致重新进行散列,这就可能使 map 的遍历结果在扩容前后变得不可靠,Go 设计者为了让大家不依赖遍历的顺序,每次遍历的起点--即起始 bucket 的位置不一样,即不让遍历都从某个固定的 bucket0 开始,所以即使未扩容时我们遍历出来的 map 也总是无序的。
map 类型的切片
假设我们想获取一个 map 类型的切片,我们必须使用两次 make() 函数,第一次分配切片,第二次分配切片中每个 map 元素
package main
import "fmt"
func main() {
// Version A:
items := make([]map[int]int, 5)
for i:= range items {
items[i] = make(map[int]int, 1)
items[i][1] = 2
}
fmt.Printf("Version A: Value of items: %v\n", items)
// Version B: NOT GOOD!
items2 := make([]map[int]int, 5)
for _, item := range items2 {
item = make(map[int]int, 1) // item is only a copy of the slice element.
item[1] = 2 // This 'item' will be lost on the next iteration.
}
fmt.Printf("Version B: Value of items: %v\n", items2)
}
输出:
Version A: Value of items: [map[1:2] map[1:2] map[1:2] map[1:2] map[1:2]]
Version B: Value of items: [map[] map[] map[] map[] map[]]
需要注意的是,应当像 A 版本那样通过索引使用切片的 map 元素。在 B 版本中获得的项只是 map 值的一个拷贝而已,所以真正的 map 元素没有得到初始化。
map 的排序(通过切片)
map 默认是无序的,不管是按照 key 还是按照 value 默认都不排序(详见第 8.3 节)。
想排序,先把map的内容拷贝到切片,再通过sort包来排序
// 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])
}
}
建议用结构体
但是如果你想要一个排序的列表,那么最好使用结构体切片,这样会更有效:
type name struct {
key string
value int
}
将 map 的键值对调
这里对调是指调换 key 和 value,前提是 map 的值类型可以作为 key 且所有的 value 是唯一的
package main
import (
"fmt"
)
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() {
invMap := make(map[int]string, len(barVal))
for k, v := range barVal {
invMap[v] = k
}
fmt.Println("inverted:")
for k, v := range invMap {
fmt.Printf("Key: %v, Value: %v / ", k, v)
}
}
如果值不唯一,一种解决方法就是仔细检查唯一性并且使用多值 map,比如使用 map[int][]string 类型