Map 是一种无序的键值对的集合(=**一种无序的基于key-value的数据结构**)。
使用:
- Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
- Go语言中的map是引用类型,必须初始化才能使用。
- Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
基本语法
声明/初始化/赋值
| 全局 | 局部 | |
|---|---|---|
| 声明 | var a <font style="color:rgb(0, 134, 179);">map</font>[string]int |
|
| 范围 | var a |
a := |
| 初始化为零值 | 等式右边**<font style="color:rgb(51, 51, 51);">make</font>**(**<font style="color:rgb(51, 51, 51);">map</font>**[string]int, 8)/<font style="color:#c7773e;">map</font>[<font style="color:#c7773e;">int</font>]<font style="color:#c7773e;">int</font>{} |
|
| 初始化 | ![]() 或者 下标赋值 map_01[key] = ....(value) |
|
| 赋值 | 赋值都在函数块内进行(声明之后) 1. map_01 = map[string]int {...:..., ...:..., } 注意,map[key_type] value_type是数据的一部分,和python用{} 和[]区别很不一样2. map_01[key] = ....(value) 下标赋值 |
:::info 要求
:::
- key在集合中是唯一的,定义之后不可修改的,value是可修改的
- 初始化
- 集合使用的要求——必须初始化才能使用,原因:指针,初始化有两种方式,集合使用前一定会经过两者其中一步:
- 声明时直接赋值,可为零值
- 通过make函数进行内存分配
- 若未初始化,则默认为nil
这里需要注意,经过make初始化得到的map,其key和value为对应其类型的零值,如int—0,char—“”,和nil是不一样的
- 集合使用的要求——必须初始化才能使用,原因:指针,初始化有两种方式,集合使用前一定会经过两者其中一步:
:::info make函数创建map
:::
- 语法
<font style="color:rgb(36, 41, 46);">map_1 := make([key_type]value_type,cap)</font>- 其中cap表示map的容量,cap可省略,但是我们应该在初始化map的时候就为其指定一个合适的容量。避免不断地扩容,会引起底层数据的不断创建与赋值,最好在开始的时候就赋予好容量
- 集合map是不能求cap的(其实我内心非常的无语)
- make函数设的是cap
- 计算长度,可用
<font style="color:rgb(36, 41, 46);">len(map_1)</font>,实际多少就多少;用make函数构建map可以设cap,但不可用<font style="color:rgb(36, 41, 46);">cap(map_1)</font>求cap
:::info 值和类型
:::
比如我们来分析map_1 := map[string]int{"justin": 1, "Bob":2, "Tom": 3}
- 类型——
map[string]int - 值——
map[string]int{"justin": 1, "Bob":2, "Tom": 3}注意和python(用符号区分)不一样的是,开头的类型,也是值的一部分 - 有一个类型需要注意,那就是结构类型
调用
- 无range
v = map_01[key]<font style="color:#AD1A2B;">k, v = map_01[key]</font>是错误的!!!<font style="color:#AD1A2B;">value, ok := map_1[key]</font>才是对的!!!用来判断键值是否存在
- 有range
- 但是
<font style="color:#AD1A2B;">for k,v := range map_1{...}</font>k和v是获得的分别是key和value - 也可以
<font style="color:#AD1A2B;">for k := range map_1{...}</font>,等价于<font style="color:#AD1A2B;">for k,_ := range map_1{...}</font>
- 但是
修改
修改已有键值对
map_1[defined_key] = new_value
如果value是结构体或者数组,可以整体替换value,但不可以修改,详情
添加
map_1[undefined_key] = undefined_value且会自动扩容
删除
使用delete()函数删除键值对——**<font style="color:rgb(51, 51, 51);">delete</font>**(**<font style="color:rgb(51, 51, 51);">map</font>**, **<font style="color:rgb(51, 51, 51);">key</font>**)
NOTE:字典不会收缩内存
## 应用
### map的遍历
使用for range循环,其遍历结果是无序的
go
func main() {
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
scoreMap["王五"] = 60
for k, v := range scoreMap {
fmt.Println(k, v)
}
}
1. 至于想不想要key和value
1. 一方面可以用_下划线省略其中一个
2. 或者,只想要k还可以:
2. 打印key和value,记得对应其数据类型输出就好
:::info
按照指定顺序遍历map
:::
func main() {rand.Seed(time.Now().UnixNano()) //初始化随机数种子var scoreMap = make(map[string]int, 200)for i := 0; i < 100; i++ {key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串value := rand.Intn(100) //生成0~99的随机整数scoreMap[key] = value}//取出map中的所有key存入切片keysvar keys = make([]string, 0, 200)for key := range scoreMap {keys = append(keys, key)}//对切片进行排序sort.Strings(keys)//按照排序后的key遍历mapfor _, key := range keys {fmt.Println(key, scoreMap[key])}}
判断某个键值对是否存在
:::info 看一个现象
:::
package mainimport "fmt"func main() {scoreMap := make(map[string]int)scoreMap["小明"] = 100scoreMap["小王"] = 0x := scoreMap["张三"]//未定义y := scoreMap["小王"]//定义值为0fmt.Printf("x=%d, y=%d",x, y)}
结果

不存在的键值对,按照 map[key] = value格式,会输出其定义返回值类型的零值(本例子其value类型位int,则输出为0),而不是nil,也不会出错(和其他语言不一样,不存在的键值对输出,go语言并不会报错)
由以上看出,不存在的键值对,和存在但设其value为零值的键值对,按照 map[key] = value格式,其value都输出为0,那怎么办呢?看下方
:::info 那怎么判断这个键值对存不存在呢?
:::
语法:value, ok := map_1[key]
(注意这次value是在前面,ok获得的只能是true/false,无论value是什么类型)
如果key存在,则ok为true,value为对应的值;不存在ok为false,value为值类型的零值
package mainimport "fmt"func main() {scoreMap := make(map[string]int)scoreMap["小明"] = 100// 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值v, ok := scoreMap["张三"]//未定义键值if ok {fmt.Println(v)} else {fmt.Println("查无此人")}fmt.Println(ok)}
结果

map做函数参数
引用传递

组合
value为结构体或者数组
:::info 如果value是结构体或者数组,那就不一样了,因为内存访问安全和哈希算法等缘故,字典被设计成“not address”,故不能直接修改value成员
:::
- 这样是可以的

- 这样就不行

package mainimport "fmt"func main() {type user struct{name stringage byte}map_1 := map[int]user{1:{"Tom", 19},}map_1[1] = user{"justin", 25}fmt.Println(map_1)}
- 那对于第二种情况,怎么办呢?——两个办法
- 返回整个value给一个变量u,进行修改,然后再替换掉原value

- 返回整个value给一个变量u,进行修改,然后再替换掉原value
package mainimport "fmt"func main() {type user struct{name stringage byte}map_1 := map[int]user{1:{"Tom", 19},}u := map_1[1]u.age +=1map_1[1] = ufmt.Println(map_1)}
2. 使用指针结构体做value

package mainimport "fmt"func main() {type user struct{name stringage byte}map_1 := map[int]*user{1:&user{"Tom", 19},}map_1[1].age++ //(*map_1)[1].age++ 也行,Go语言给的结构体指针的特殊使用性质fmt.Println(*map_1[1])}
:::info value为数组
:::
使用数组

改进方法:推荐使用切片

元素为map类型的切片
func main() {var mapSlice = make([]map[string]string, 3)\\对比一般切片 s:= make([]int,3,3)\\这里创建了一个元素为集合,元素个数为3的切片\\未初始化前的打印,注意打印集合用的%vfor index, value := range mapSlice {fmt.Printf("index:%d value:%v\n", index, value)}fmt.Println("after init")//对切片中的map元素进行初始化//先用make函数初始化!!!//第一个切片元素,容量为10mapSlice[0] = make(map[string]string, 10)mapSlice[0]["name"] = "王五"mapSlice[0]["password"] = "123456"mapSlice[0]["address"] = "红旗大街"for index, value := range mapSlice {fmt.Printf("index:%d value:%v\n", index, value)}}

value为切片类型的map
func main() {//切片len= 3,元素为map//一般map声明 var s = make(map[string]int, 3),对比,value类型就是[]stringvar sliceMap = make(map[string][]string, 3)fmt.Println(sliceMap)fmt.Println("after init")key := "中国"value, ok := sliceMap[key]if !ok {value = make([]string, 0, 2)}value = append(value, "北京", "上海")sliceMap[key] = valuefmt.Println(sliceMap)}

我自己修改过的代码

?Map与工厂模式
- Map的value可以是一个方法
- 与Go的Docker type接口方式一起,可以方便地实现但一方法对象的工厂模式

map不是没顺序的吗?
?Set的实现
go没有内置set,但是可以通过map实现,可以 map[type]bool
- 元素的唯一性
- 基本操作
- 添加元素
- 判断元素是否存在
- 删除元素
- 元素个数

