Go map基本用法与采坑

309 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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存在并发读写安全问题,这个也是很容易掉坑的,后续分享并发时再来分享,期待下。

如果本文对你有帮助,欢迎点赞收藏加关注,如果本文有错误的地方,欢迎指出!