重学Go语言 | Go Map详解
map
是Go
语言中另外一种引用数据类型,也是比较常用的数据类型,今天在这篇文章中,我们来讨论一下。
什么是map
map
,中文称为映射或者哈希表,其结构如下图所示:
在Go
语言中,用map[KeyType]ValueType
表示一个map
,其中,KeyType
表示key
的数据类型,ValueType
表示value
的数据类型:
var students map[string]int
map
是一个无序的键值对(key-value
)集合,其底层数据结构是一个哈希表,通过哈希函数,将key
转换为对哈希表中的索引,将value
存储到索引对应的位置,在map
中查找、删除、查找value的时间复杂度O(1)
。
map的底层结构示意图:
创建map
由于map
是引用数据类型,其底层引用一个哈希表,因此未经初始化(即未分配到内存空间)的map
无法直接使用:
var m map[string]int
m["a"] = 1
//未初始化,报错
初始化map有两种方式:
- 使用make函数
- 字面量初始化
make函数
内置函数make可以为map
类型的变量分配内存:
m := make(map[string]string)
m["name"] = "test"
m["age"] = "12"
字面量初始化
通过字面量初始化map
时,可以给map
中的key
和value
赋初始值:
m := map[string]string{
"name":"test",
"age":"12",
}
如果不想给map初始化数据,也可以声明一个空的map类型:
m := map[string]string{}
注意,空的map已经初始化好了,只是没有存入值而已,而直接声明一个map变量时,该map变量为nil,这两者不一样
key数据类型限制
map的value可以是Go支持的任意数据类型,而key则所有限制:
key的数据类型必须是可以使用=
和!=
进行比较
所以,key不能是函数、切片、map,因此这些数据类型不能进行比较,另外,而数组和结构体则可以作为map的key,不过,如果数组的元素包含函数、切片、map,则数组不能作为map的key,结构体的字段如果有以上三者,也同样不能作为map的key。
type Test struct {
ID string
Name string
}
//正确
m := map[Test]int{}
type Test struct {
ID string
Name string
scores []int
}
//报错
m := map[Test]int{}
key的值必须是唯一,同一个map中不能相同的两个key
m := map[string]string{
"name":"小明",
"age":"18",
"name":"小张"//报错,不能有相同的key
}
map的特征
- map是无序的
- map的key是唯一的
- map是引用数据类型,因此在使用前必须初始化
- 函数,切片,map等数据类型不能作为map的key。
map相关操作
初始化好map
之后,我们可以对map
进行相关操作:
- 访问或给元素值赋值
- 遍历map
- 检测key是否存在
- 获取map长度
- 删除map的value。
访问map值
通过key,可以访问map变量中的value:
rank : = map[string]int{
"PHP":90,
"Go":99
"Java":95
}
fmt.Println(rank["PHP"])
如果对应的key不存在,则会返回对应value数据类型的空值,比如value为string,则返回空字符串:
//因为value为int
fmt.Println(rank["Python"])
// 输出:0
m := map[string]string{"name":"小张"}
//由于value为string类型
fmt.Println(m["age"])
//输出空字符串
判断key是否存在
如果我们想在通过key访问map之前就确定对应的key是否存在,有另外一种写法:
v, ok := m[k]
上面的表达式中,有第二个返回值ok
,该值为boolean类型,当key存在时,ok
的值为true,v
为对应的value
;否则为ok
为false
,v
为空值。
v,ok := rank[k]
if ok {
fmt.Println(v)
}
if v,ok := rank[k];ok{
fmt.Println(v)
}
不能对map的value进行取址操作
Go的数组和切片允许对元素进行取址操作,但不允许对map的元素进行取址操作:
//对数组元素取址
a := [3]int{1, 2, 3}
fmt.Println(&a[1])
//对切片元素取址
s := []int{1, 2, 3}
fmt.Println(&s[1])
m := map[string]string{
"test":"test",
}
//对map元素取址,错误
fmt.Println(&m["test"])
为什么Go要限制对map的元素取址呢?
因为Go可以在添加新的键值对时更改键值对的内存位置。Go将在后台执行此操作,以将检索键值对的复杂性保持在恒定水平。因此,地址可能会变得无效,Go宁愿禁止访问一个可能无效的地址。
遍历map
使用for-range
语句可以遍历map
,获得map
的key
和value
:
m := map[string]string{
"name":"xiaoming",
"age":"18岁"
}
for k,v := range m{
fmt.Println(k,v)
}
map的排序
map
是无序的,所以每次遍历map
输出的顺序都不一样相同
var user = map[string]string{
"id": "0001",
"name": "小张",
"age": "18岁",
}
for k, v := range user {
fmt.Println(k, ":", v)
}
for k, v := range user {
fmt.Println(k, ":",v)
}
要有序地遍历一个map类型的变量,可以这么做:
order := []string{}
for k, _ := range user {
order = append(order, k)
}
for _, v := range order {
fmt.Println(user[v])
}
for _, v := range order {
fmt.Println(user[v])
}
获取长度
要获取map的长度,同样是用内置的len
函数:
var user = map[string]string{
"id": "0001",
"name": "小张",
"age": "18岁",
}
fmt.Println(len(user))
//输出:3
删除map的value
要删除map的value,可以使用Go内置的delete
函数,该函数格式如下:
func delete(m map[KeyType]ValueType, key Type)
该函数的第一个参数是我们要操作的map类型的变量,第二个参数表示要删除哪个key:
m := map[string]int{
"a":1,
"b":2
}
fmt.Println(m)
delete(m,"a")
fmt.Println(m)
map的比较
map
类型变量之间不能进行比较,map
只能与nil
进行比较:
var m map[int]string
//判断是否等于nil
if m == nil{
fmt.Println("m hasn't been initialized")
}
m1 := map[string]string{"name": "小明"}
m2 := map[string]string{"name": "小明"}
//报错
if m1 == m2 {
fmt.Println("相等")
}
map嵌套
由于map
的value
并没有数据类型的限制,所以value
也可以是另一个map
类型:
理论上map嵌套map可以一直嵌套下去,但一般不会这么做的。
mm := map[string]map[int]string{
"a": {1: "test1"},
"b": {2: "test2"},
"c": {2: "test3"},
}
fmt.Println(mm)
小结
关于Go的map数据类型已经讲解完了,总结起来主要了以下几点:
- 什么是map,如何创建map。
- map中key的限制。
- map的相关操作:访问,赋值,遍历,判断key是否存在,获取长度等。
- map的嵌套。
转载自:https://juejin.cn/post/7247027677222502458