写点什么

Go 学习笔记——符合数据类型

作者:为自己带盐
  • 2022 年 2 月 25 日
  • 本文字数:2908 字

    阅读完需:约 10 分钟

Go学习笔记——符合数据类型

书接上回《Go 学习笔记——同构复合类型》

一、关于 map 的定义和要点

  • map 类型是一个无序的键值对的集合。它有两种类型元素,一类是键(key),另一类是值(value)。在一个 map 中,键是唯一的,在集合中不能有两个相同的键。Go 也是通过这两种元素类型来表示一个 map 类型,要记得这个通用的 map 类型表示:“map[key_type]value_type”

  • map 的 key 类型,必须要支持==和!=操作符,常用的 key 类型是 int,string,自定义的可比较的数据结构等,而函数类型、map 类型自身,以及切片类型是不能作为 map 的 key 类型的!

//声明map类型时,可以是这样(注意短变量类型的声明要在局部范围内)m1 := map[int][]string{1: []string{"val1_1", "val1_2"}, 3: []string{"val3_1", "val3_2", "val3_3"}, 7: []string{"val7_1"}}//也可以是这样m := make(map[int]string)//或者是这样mm := map[string]int{		"key1": 1,		"key2": 2,	}
复制代码
  • map 和切片不同的时,切片时”零值可用“的,而 map 不是,也就是 map 在定义时,需要设定初始值。通常有两种做法,

a、一是使用复合字面值初始化 map 类型变量(说起来好象有点绕口,其实就是在定义的时候给个值而已),这里需要注意,go 为这种定义 map 变了的方式提供了语法糖,也就是我们在使用字面值初始化 map 变量时,写法不用那么臃肿~~

//复合字面值定义map变量的书面写法
m1 := map[int][]string{ 1: []string{"val1_1", "val1_2"}, 3: []string{"val3_1", "val3_2", "val3_3"}, 7: []string{"val7_1"},}
type Position struct { x float64 y float64}
m2 := map[Position]string{ Position{29.935523, 52.568915}: "school", Position{25.352594, 113.304361}: "shopping-mall", Position{73.224455, 111.804306}: "hospital",}
复制代码


//采用go提供的语法糖后,和变量的定义是一样的,go编译器很聪明,很多时候你完全不用告诉它所有事情m1 := map[int][]string{    1: {"val1_1", "val1_2"},    3: {"val3_1", "val3_2", "val3_3"},    7: {"val7_1"},}
type Position struct { x float64 y float64}
m2 := map[Position]string{ {29.935523, 52.568915}: "school", {25.352594, 113.304361}: "shopping-mall", {73.224455, 111.804306}: "hospital",}
复制代码

b、二是使用内置函数 make

m1 := make(map[int]string) // 未指定初始容量m2 := make(map[int]string, 8) // 指定初始容量为8
复制代码

不过,map 类型的容量不会受限于它的初始容量值,当其中的键值对数量超过初始容量后,Go 运行时会自动增加 map 类型的容量,保证后续键值对的正常插入。

二、map 基本操作

1. 插入、更新键值

这里的注意点和其他语言基本类似,定义好 map 类型后,只需要把 value 赋给 key 就完成了插入操作,需要注意的时,如果 map 中存在某个 key,在此进行赋值,则新的值会替代掉原有的值。所以,在 map 中插入数据时,不用自己判定是否插入成功,因为结果总是成功的。

m := make(map[int]string)m[1] = "value1"m[2] = "value2"m[3] = "value3"fmt.Println(m[1]) //value1m[1] = "value1111"fmt.Println(m[1]) //value111
复制代码

2. 获取键值对数量

通过内置函数 len 来获取,这里需要注意,不能对 map 类型调用 cap 函数,这是 map 和 slice 的一处不同

mm := map[string]int{		"key1": 1,		"key2": 2,}mm["key1"] = 11mm["key3"] = 3fmt.Println(len(mm)) //3
复制代码

3. 查找和读取数据

map 的数据结构,让其更加广泛的用在查找和读取场合。

查找就是判定某个键值是否存在 map 中,而读取一般就是读取该键值在 map 中对应的值

需要注意的时,判定 key 是否存在 map 中的时候,需要使用 go 推荐的“comma ok”用法。因为 map 不是零值可用类型,也就是当我们从 map 中随便获取一个不存在的 key,也会得到一个零值(比如 0,""等),而我们无法判定这个零值是本身的数据,还是因为键值不存在而返回的一个零值。

而“comma ok”用法可以帮我们解决这个问题

//不存在的情况mm := map[string]int{		"key1": 1,		"key2": 2,}v, ok := mm["key11"]if !ok {  fmt.Println("key11不存在")//输出}fmt.Println(v);//0//存在的情况mm := map[string]int{		"key1": 1,		"key2": 2,}
v, ok := mm["key1"]if !ok { fmt.Println("key1不存在")//不输出}fmt.Println(v);//1
复制代码

4. 删除数据

通过内置的 delete 函数来从 map 中删除数据,这是删除键唯一的方法。

m := map[string]int {  "key1" : 1,  "key2" : 2,}
fmt.Println(m) // map[key1:1 key2:2]delete(m, "key2") // 删除"key2"fmt.Println(m) // map[key1:1]
复制代码

5. 遍历 map 中的键值数据

遍历 map 的键值对只有一种方法,那就是像对待切片那样通过 for range 语句对 map 数据进行遍历


package main import "fmt"
func main() { m := map[int]int{ 1: 11, 2: 12, 3: 13, }
fmt.Printf("{ ") for k, v := range m { fmt.Printf("[%d, %d] ", k, v) } fmt.Printf("}\n")}
复制代码


如果不需要键值,则可以使用空“_”标识符来代替


for k, _ := range m { fmt.Printf("[%d] ", k)// 使用k}
复制代码

或者更直接一点

for k := range m {   fmt.Printf("[%d] ", k)// 使用k}
复制代码

同样,如果只关心 value 则可以这样


for _, v := range mm { fmt.Printf("[%d] ", v)}
复制代码

注意,只要 value 的话就不能再直接了哦~

另外,对同一 map 做多次遍历的时候,每次遍历元素的次序都不相同,也就是程序逻辑千万不要依赖遍历 map 所得到的的元素次序。


package main import "fmt"
func doIteration(m map[int]int) { fmt.Printf("{ ") for k, v := range m { fmt.Printf("[%d, %d] ", k, v) } fmt.Printf("}\n")}
func main() { m := map[int]int{ 1: 11, 2: 12, 3: 13, }
for i := 0; i < 3; i++ { doIteration(m) }}//最终三次的输出结果是//{ [3, 13] [1, 11] [2, 12] }//{ [1, 11] [2, 12] [3, 13] }//{ [3, 13] [1, 11] [2, 12] }
复制代码

三、map 变量的传递开销

map 的传递是一个“描述符”,而不是整个数据的拷贝,所以开销固定,也很小

当 map 变量被传递到函数或方法内部后,我们在函数内部对 map 类型参数的修改在函数外部也是可见的

package main  import "fmt"
func foo(m map[string]int) { m["key1"] = 11 m["key2"] = 12}
func main() { m := map[string]int{ "key1": 1, "key2": 2, } fmt.Println(m) // map[key1:1 key2:2] foo(m) fmt.Println(m) // map[key1:11 key2:12] }
复制代码

四、内部实现和扩容

我先跳过,后续再回头来研究(也在学习这门课同学药根据实际情况丫,我是最近项目多,深入原理会增加心智负担和时间成本)

关于扩容,只记录一个要点,map 中数据元素的 value 位置可能在扩容过程中发生变化,所以 Go 不允许获取 map 中 value 的地址,这个约束是在编译期间就生效的。

五、map 与并发

我这里暂时只记录一个结论,那就是 map 不是线程安全的,对并发不友好


好了,这篇看起来很多,但都是很实用的东西,除了原理部分,整体都比较简单。

发布于: 刚刚阅读数: 3
用户头像

学着码代码,学着码人生。 2019.04.11 加入

狂奔的小码农

评论

发布
暂无评论
Go学习笔记——符合数据类型