开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3天,点击查看活动详情
前言
PHP基于array关联数组,实现了映射表数据的存储。上文《Go既然有数组为啥还要有切片》学习了Go的数组和切片,用这两种数据类型来存储映射表,也是实现不了。所以今天我们得来学习下Go的map这个数据类型,支持映射表数据的存储。
定义
map是一种无序的基于key-value的数据结构,Go提供的映射关系的容器,用于存储映射表相关数据。
注意:map是引用类型,必须初始化后才能使用
声明和初始化
两种方式:
//方式1:普通写法
m := map[string]interface{}{
"name": "zhangsan",
"age": 18,
}
m["address"] = "xxxx"
fmt.Println(m) //map[address:xxxx age:18 name:zhangsan]
//方式2:使用make
m2 := make(map[string]interface{}, 0) //cap=0,这个cap非必填
m2["name"] = "zhangsan"
m2["age"] = 18
fmt.Println(m2) //map[age:18 name:zhangsan]
引用类型,修改原值
由于map也是引用类型,赋值后,修改对应key的值,都会相互影响生效
m := map[string]interface{}{
"name": "zhangsan",
"age": 18,
}
m["address"] = "xxxx"
fmt.Println(m) //map[address:xxxx age:18 name:zhangsan]
m3 := m
m3["name"] = "lisi"
fmt.Println(m) //map[address:xxxx age:18 name:lisi]
fmt.Println(m3) //map[address:xxxx age:18 name:lisi]
不可对比
map和切片一样,都只能和nil对比;map和map之间不能对比,会编译失败
var m map[string]interface{}
fmt.Println(m == nil) //true
m = map[string]interface{}{
"name": "zhangsan",
"age": 18,
}
fmt.Println(m == nil) //false
m2 := map[string]interface{}{
"name": "lisi",
"age": 20,
}
fmt.Println(m == m2) // 编译失败: invalid operation: m == m2 (map can only be compared to nil)
判断key是否存在
使用精妙的ok写法:
m := map[string]interface{}{
"name": "zhangsan",
"age": 18,
}
m["address"] = "xxxx"
//存在ok=true,v为对应值;不存在ok=false,v为零值
if v, ok := m["name"]; ok {
fmt.Println("存在", v)
} else {
fmt.Println("不存在", v)
}
//输出:存在 zhangsan
删除键值对
处理业务逻辑,有时需要把一些非必要的字段给剔除掉,map则可以使用delete函数来操作
m := map[string]interface{}{
"name": "zhangsan",
"age": 18,
}
m["address"] = "xxxx"
fmt.Println(m) //map[address:xxxx age:18 name:zhangsan]
delete(m, "address")
fmt.Println(m) //map[age:18 name:zhangsan]
遍历map
使用for...range,会返回map的键和值
m := map[string]interface{}{
"name": "zhangsan",
"age": 18,
}
m["address"] = "xxxx"
fmt.Println(m) //map[address:xxxx age:18 name:zhangsan]
str := ""
for k, v := range m {
str += fmt.Sprintf("%v:%v", k, v) + " --- "
}
fmt.Println(str) //name:zhangsan --- age:18 --- address:xxxx ---
采坑
1. map遍历是无序的,如何保证有序呢
尤其是在php服务切go,这个无序问题很痛,因为php array有序,go map无序(Go底层实现规定)
解决方案:借用slice来辅助排序,但需要注意没法和php array顺序完全一致,只能做到相对有序
func main() {
m := map[string]string{
"name": "zhangsan",
"age": "18",
"address": "xxxx",
}
//先看下无序输出
each(m) //第一次:name:zhangsan --- age:18 --- address:xxxx ---
each(m) //第二次:age:18 --- address:xxxx --- name:zhangsan ---
//开始处理有序
keys := make([]string, 0, len(m)) //使用slice存储key,然后排序
for k, _ := range m {
keys = append(keys, k)
}
sort.Strings(keys)
fmt.Println(keys) //[address age name]
str := ""
for _, key := range keys {
str += fmt.Sprintf("%v:%v", key, m[key]) + " --- "
}
fmt.Println(str) //address:xxxx --- age:18 --- name:zhangsan --- //有序输出,不再无序
}
func each(m map[string]string) {
str := ""
for k, v := range m {
str += fmt.Sprintf("%v:%v", k, v) + " --- "
}
fmt.Println(str)
}
2.使用map一不小心就panic
错误案例:
var m map[string]string
m["name"] = "zhangsan"
fmt.Println(m)
//panic: assignment to entry in nil map
为什么会panic呢?因为map是引用类型,必须要初始化后才能使用
正确写法:
var m map[string]string
m = make(map[string]string) //使用make做初始化,并分配地址
m["name"] = "zhangsan"
fmt.Println(m, fmt.Sprintf("%p", m)) //map[name:zhangsan] 0xc000100030
3.修改结构体map的正确姿势
错误案例:
func main() {
m := make(map[string]person)
m["zhangsan"] = person{
name: "zhangsan",
age: 18,
}
m["zhangsan"].age = 60
fmt.Printf("%+v\n", m)
//编译失败,报错: cannot assign to struct field m["zhangsan"].age in map
}
原因分析:map 中的元素为结构体时, 是不可寻址的,所以不允许直接修改
正确姿势1:使用指针结构体(既然结构体不行,那么我就用指针结构体,做到可寻址)
func main() {
m := make(map[string]*person)
m["zhangsan"] = &person{
name: "zhangsan",
age: 18,
}
m["zhangsan"].age = 60
fmt.Printf("%+v\n", m) //map[zhangsan:0xc0000b0018]
}
正确姿势2:使用临时变量,然后重新覆盖
func main() {
m := make(map[string]person)
m["zhangsan"] = person{
name: "zhangsan",
age: 18,
}
p1 := m["zhangsan"] //p1临时变量
p1.age = 60
m["zhangsan"] = p1
fmt.Printf("%+v\n", m) //map[zhangsan:{name:zhangsan age:60}]
}
总结
存储映射表的数据,Go得使用map类型来存储,掌握好基本用法的同时,对易采坑点,先有个认识,在实践中持续积累与沉淀。
抛砖引玉:map存在并发读写安全问题,这个也是很容易掉坑的,后续分享并发时再来分享,期待下。
如果本文对你有帮助,欢迎点赞收藏加关注,如果本文有错误的地方,欢迎指出!