爱上开源之golang入门至实战第四章-映射(Map)

网友投稿 241 2022-08-23

爱上开源之golang入门至实战第四章-映射(Map)

前言

键值对的集合是很多语言都支持的基础数据结构,GO语言里也有同样的键值对集合的数据类型,那就是Map。

Map 是一种无序的键值对的集合。Map可以通过 key 来快速检索数据,key类似于索引,指向数据的值。我们可以像迭代数组和切片那样迭代它。但是因为 Map 是使用 hash 表来实现的;Map 是无序的,无法决定它的返回顺序。Map和切片一样都是Go语言编程里使用非常广泛的数据结构,我们也要熟悉的掌握Map数据类型的使用。

今天就来谈谈go语言里的Map

var variable_name map[keytype]valuetype 例如var students map[string]Student

keytype定义的数据类型可以是任意可以用 == 或者 != 操作符比较的类型,k1 == k2 时,可认为 k1 和 k2 是同一个 key。Go 语言中只要是可比较的类型都可以作为 key。除开 slice,map,functions 这几种类型,其他类型都是作为key的对象的。具体包括:布尔值、数字、字符串、指针、通道、接口类型、结构体、只包含上述类型的数组。结构体的比较需要结构体定义的字段类型都支持可比较。如果是结构体,只有 hash 后的值相等以及字面值相等,才被认为是相同的 key。很多字面值相等的,hash出来的值不一定相等,比如引用。

valuetype可以是任何数据类型,任何类型都可以作为value;通过使用空接口类型, 比如 any, interface{} 我们可以存储任意值,也可以使用函数类型来定义成值类型;这个经常在一些复杂的命令控制程序代码里出现;

如果不初始化 map,那么就会创建一个值为nil的Map对象。nil Map对象不能用来存放键值对。程序运行时,对nil Map对象进行访问时,就会报panic错误。 所以如果要使用一个Map对象,一定要进行初始化或者赋值。这一点上和Slice稍微有点区别。

4.3.2 初始化

map 可以用 {key1: val1, key2: val2} 的描述方法来初始化,就像数组和结构体一样。

var m map[string]stringm = map[string]string{"Go": "Google", "Java": "Oracle"}fmt.Println(m)==== OUTPUT ====map[Go:Google Java:Oracle]

或者直接写成

m := map[string]string{"Go": "Google", "Java": "Oracle"}fmt.Println(m)==== OUTPUT ====map[Go:Google Java:Oracle]

下面是一个结构体作为Value的初始化

students := map[int]struct { name string sex int age int}{ 1: {"user1", 1, 20}, 2: {"user2", 2, 20}, 3: {"user2", 2, 21},}fmt.Println(students)==== OUTPUT ====map[1:{user1 1 20} 2:{user2 2 20} 3:{user2 2 21}]

map 是 引用类型 的: 内存用 make 方法来分配。可以通过make方法来对map进行初始化;

c := make(map[string]string)c["Go"] = "Google"c["Java"] = "Oracle"fmt.Println(c)==== OUTPUT ====map[Go:Google Java:Oracle]

s := make(map[int]any)s[1] = struct { name string sex int age int}{"user1", 1, 20}s[2] = struct { name string sex int age int}{"user2", 2, 20}s[3] = struct { name string sex int age int}{"user2", 2, 21}fmt.Println(s)==== OUTPUT ====map[1:{user1 1 20} 2:{user2 2 20} 3:{user2 2 21}]

当 map 到容量上限的时候,如果再增加新的键值对,map 的大小会自动加 1。出于对性能的考虑,对于大的 map或者会快速扩张的 map,即使不是非常明确具体的容量大小,也最好先根据初略的计算,大概的标明Map的大小。 在使用make初始化Map的时,可以预先给Map指定⼀个合理元素数量,在初始化时先申请⼀⼤块内存,而可以避免后续操作时频繁扩张。

例如

map2 := make(map[string]float32, 100)

4.3.3 赋值及访问

map在中的对象可以通过key进行赋值或者访问

s := make(map[int]any)s[1] = struct { name string sex int age int}{"user1", 1, 20}s[2] = struct { name string sex int age int}{"user2", 2, 20}s[3] = struct { name string sex int age int}{"user2", 2, 21}fmt.Println(s[0])fmt.Println(s[1])fmt.Println(s)==== OUTPUT ===={user1 1 20}map[1:{user1 1 20} 2:{user2 2 20} 3:{user2 2 21}]

如上个例子所示,s为Map对象,其中key值分别有1,2,3;访问key值为0的时候,key不存在;返回nil;

访问Map里不存在的Key返回nil对象; 由于Map的value可以是任何类型的值,也包括nil,所以我们可以将一个nil对象作为value放入Map对象, 时候返回值也为nil,但是key是存在的; 我们可以通过下面的方式来进行key的判断;一般判断是否某个key存在,不使用值判断,

s := make(map[int]any)s[1] = struct { name string sex int age int}{"user1", 1, 20}s[2] = struct { name string sex int age int}{"user2", 2, 20}s[3] = struct { name string sex int age int}{"user2", 2, 21}s[4] = nilfmt.Println(s)fmt.Println(s[1])fmt.Println(s[0])fmt.Println(s[4])if v, ok := s[0]; !ok { fmt.Println("不存在")} else { fmt.Printf("存在 %+v \n", v)}if v, ok := s[4]; !ok { fmt.Println("不存在")} else { fmt.Printf("存在 值=%+v \n", v)}if v, ok := s[1]; !ok { fmt.Println("不存在")} else { fmt.Printf("存在 值= %+v \n", v)}==== OUTPUT ====map[1:{user1 1 20} 2:{user2 2 20} 3:{user2 2 21} 4:]{user1 1 20}不存在存在 值= 存在 值= {name:user1 sex:1 age:20}

4.3.4 迭代Map

非常多的时候,我们需要遍历整个Map所有的Key,并根据获取到的Key和对应的Value对象进行操作;此时我们的可以使用range方式来实现这个目的, 例如

s := make(map[int]any) s[1] = struct { name string sex int age int }{"user1", 1, 20} s[2] = struct { name string sex int age int }{"user2", 2, 20} s[3] = struct { name string sex int age int }{"user2", 2, 21} s[4] = nil fmt.Println(s) for k, o := range s { fmt.Printf("%d=%+v \n", k, o) }==== OUTPUT ====map[1:{user1 1 20} 2:{user2 2 20} 3:{user2 2 21} 4:]1={name:user1 sex:1 age:20} 2={name:user2 sex:2 age:20} 3={name:user2 sex:2 age:21} 4=

由于Map是无序的,迭代的过程不能保证返回次序,返回的顺序;通常是和具体环境和版本实现有关。

4.3.5 值传递和引用传递

Map对象本身,在作为参数和返回值的时候,都是使用的引用传递;如下面的示例

setName := func(a map[string]string, name string) map[string]string { a["name"] = name return a}sample := map[string]string{"name": "Go"}fmt.Println(sample) // map[name:Go]setName(sample, "Java")fmt.Println(sample) // map[name:Java]two := setName(sample, "C#")fmt.Println(two) // map[name:C#]two["name"] = "Python"fmt.Println(sample) // map[name:Python]==== OUTPUT ====map[name:Go]map[name:Java]map[name:C#]map[name:Python]

与切片一样,切片有 ​​copy​​函数来进行值的复制;map 却没有,目前只能通过遍历然后重新赋值来实现。

通过Map的Key访问获取的值对象,不是引用传递,是指传递

如下列所示

type student struct { name string sex int age int}s1 := make(map[int]student)s1[1] = student{"user1", 1, 20}s1[1].name = "User01" //Cannot assign to s1[1].name==== Complie ERROR====.\build_test.go:134:2: cannot assign to struct field s1[1].name in map

通过range的方式,进行迭代,或者值对象也是值传递

type student struct { name string sex int age int}s1 := make(map[int]student)s1[1] = student{"user1", 1, 20}setName2 := func(a map[int]student, id int, newName string) map[int]student { if v, ok := a[id]; ok { v.name = newName } return a}fmt.Println(s1) // map[1:{user1 1 20}]setName2(s1, 1, "User01")fmt.Println(s1) // map[1:{user1 1 20}]s2 := setName2(s1, 1, "User02")fmt.Println(s2) // map[1:{user1 1 20}]==== OUTPUT ====map[1:{user1 1 20}]map[1:{user1 1 20}]map[1:{user1 1 20}]

如果需要进行原值的覆盖可以使用完整替换 value 或使⽤指针

type student struct { name string sex int age int}s1 := make(map[int]student)s1[1] = student{"user1", 1, 20}s1[1] = student{"User01", 1, 20}fmt.Println(s1) // map[1:{User01 1 20}] ==== OUTPUT ====map[1:{User01 1 20}]type student struct { name string sex int age int}ss := make(map[int]*student)ss[1] = &student{"user1", 1, 20}setName3 := func(a map[int]*student, id int, newName string) { if v, ok := a[id]; ok { v.name = newName }}fmt.Println(ss[1]) //{user1 1 20}setName3(ss, 1, "User01")fmt.Println(ss[1]) // {User01 1 20}==== OUTPUT ====&{user1 1 20}&{User01 1 20}

4.3.6 Delete

delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key。从 map1 中删除 key1:

直接 delete(map1, key1) 就可以。如果 key1 不存在,该操作不会产生错误。

实例如下:

Delete(map4, "a")

4.3.7 map的GC回收机制

map是只增不减的一种数组结构,那些被 delete 的 key 会被打上标记,但是不会被GC回收。对于大的map对象。如果不再使用了最好使用赋值 nil 的方式使其整个的可以被GC。

4.3.8 并发缺陷

Go语言原生的Map非并发安全的, 在多并发的情况下,如果有写的操作,会出现Panic,提示concurrent map writes的错误

mm := map[int]int{}for i := 0; i < 21; i++ { go func() { mm[1] = 1 }()}====Panic====fatal error: concurrent map writes

另外如果多线程同时 read 和 write ,或者删除 key,还会出现 fatal error: concurrent map read and map write,这都是 map 存在的并发问题。

解决问题,可以引入sync.Mutex或者sync.RWMutex控制并发访问;或是是通过第三方的并发控制进行解决。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:爱上开源之DockerUI-自建Docker镜像仓库实战
下一篇:地产营销,不能自暴自弃!(房地产营销破局)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~