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存入切片keys
var keys = make([]string, 0, 200)
for key := range scoreMap {
keys = append(keys, key)
}
//对切片进行排序
sort.Strings(keys)
//按照排序后的key遍历map
for _, key := range keys {
fmt.Println(key, scoreMap[key])
}
}
判断某个键值对是否存在
:::info 看一个现象
:::
package main
import "fmt"
func main() {
scoreMap := make(map[string]int)
scoreMap["小明"] = 100
scoreMap["小王"] = 0
x := scoreMap["张三"]//未定义
y := scoreMap["小王"]//定义值为0
fmt.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 main
import "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 main
import "fmt"
func main() {
type user struct{
name string
age 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 main
import "fmt"
func main() {
type user struct{
name string
age byte
}
map_1 := map[int]user{
1:{"Tom", 19},
}
u := map_1[1]
u.age +=1
map_1[1] = u
fmt.Println(map_1)
}
2. 使用指针结构体做value
package main
import "fmt"
func main() {
type user struct{
name string
age 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的切片
\\未初始化前的打印,注意打印集合用的%v
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
fmt.Println("after init")
//对切片中的map元素进行初始化
//先用make函数初始化!!!
//第一个切片元素,容量为10
mapSlice[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类型就是[]string
var 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] = value
fmt.Println(sliceMap)
}
我自己修改过的代码
?Map与工厂模式
- Map的value可以是一个方法
- 与Go的Docker type接口方式一起,可以方便地实现但一方法对象的工厂模式
map不是没顺序的吗?
?Set的实现
go没有内置set,但是可以通过map实现,可以 map[type]bool
- 元素的唯一性
- 基本操作
- 添加元素
- 判断元素是否存在
- 删除元素
- 元素个数