欢迎访问我的博客,目前从事Machine Learning,欢迎交流

【读书笔记&个人心得】第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 类型

posted @ 2023-03-01 11:56  有蚊子  阅读(30)  评论(0编辑  收藏  举报