Go语言容器—Map
Posted ych9527
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言容器—Map相关的知识,希望对你有一定的参考价值。
文章目录
Map的概念
- map 是引用类型,可以使用如下方式声明
- var mapname map[keytype]valuetype
- 提示:[keytype] 和 valuetype 之间允许有空格
- 在声明的时候不需要知道 map 的长度,因为 map 是可以动态增长的,未初始化的 map 的值是 nil,使用函数 len() 可以获取 map 中 pair 的数目
- var mapname map[keytype]valuetype
Map需要注意的一些点
- map的创建方式之中,map[string]int 等价于 make(map[string]int)
- := 是引用
- 可以使用 make(),但不能使用 new() 来构造 map,如果错误的使用 new() 分配了一个引用对象,会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址
var dataMap map[string]int
var quoteMap map[string]int
dataMap = map[string]int"one": 1, "two": 2 //赋值
createMap := make(map[string]int) //make创建个新的map,等价于mapCreated := map[string]int
quoteMap = dataMap //进行引用
createMap["key1"] = 10
createMap["key2"] = 20
quoteMap["two"] = 999 //引用改值
fmt.Println(dataMap) //map[one:1 two:999]
fmt.Println(createMap) //map[one:1 two:999]
Map的容量
- 和数组不同,map 可以根据新增的 key-value 动态的伸缩,因此它不存在固定长度或者最大限制,但是也可以选择标明 map 的初始容量 capacity,格式如下
- make(map[keytype]valuetype, cap)
- 当 map 增长到容量上限的时候,如果再增加新的 key-value,map 的大小会自动加 1,所以出于性能的考虑,对于大的 map 或者会快速扩张的 map,即使只是大概知道容量,也最好先标明
用切片作为Map的值
-
既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?例如,当我们要处理 unix 机器上的所有进程,以父进程(pid 为整形)作为 key,所有的子进程(以所有子进程的 pid 组成的切片)作为 value。通过将 value 定义为 []int 类型或者其他类型的切片,就可以优雅的解决这个问题,示例代码如下所示
mp1 := make(map[int][]int) //key:int value:切片,切片中元素为int mp2 := make(map[int]*[]int) //key:int value:切片,切片中元素为int*
Map的遍历
-
Go之中的map是无序的
-
map 的遍历过程使用 for range 循环完成
var dataMap map[string]int dataMap = map[string]int"one": 1, "two": 2 //赋值 for _, v := range dataMap fmt.Println(v) //2 1 //只需要键值时,无须将值改为匿名变量形式,忽略值即可。 for k := range dataMap fmt.Println(k) //one two
Map的删除和清空
-
使用 delete() 函数从 map 中删除键值对
var dataMap map[string]int dataMap = map[string]int"one": 1, "two": 2 //赋值 delete(dataMap,"one") for _, v := range dataMap fmt.Println(v) //2
-
Go语言中并没有为 map 提供任何清空所有元素的函数、方法,清空 map 的唯一办法就是重新 make 一个新的 map,不用担心垃圾回收的效率,Go语言中的并行垃圾回收效率比写一个清空函数要高效的多
Map的多键索引
-
在大多数的编程语言中,映射容器的键必须以单一值存在。这种映射方法经常被用在诸如信息检索上,如根据通讯簿的名字进行检索。但随着查询条件越来越复杂,检索也会变得越发困难。下面例子中涉及通讯簿的结构,结构如下
//人员档案结构体 type Profile struct Name string // 名字 Age int // 年龄 Married bool // 已婚 //初始化原始数据,并且实现构建索引和查询的过程 func main() list := []*Profile Name: "张三", Age: 30, Married: true, Name: "李四", Age: 21, Name: "王麻子", Age: 21, buildIndex(list) queryData("张三", 30)
-
基于哈希值的多键索引及查询
// 查询键 type classicQueryKey struct Name string // 要查询的名字 Age int // 要查询的年龄 //哈希函数 //将传入的字符串的ASCII码相加起来然后返回 func simpleHash(str string) (ret int) for i := 0; i < len(str); i++ c := str[i] ret += int(c) return ret //计算查询键的哈希值,将查询键转换成对应的查询key func (c *classicQueryKey) hash() int return simpleHash(c.Name) + c.Age*1000000 // 创建哈希值到数据的索引关系 //key:哈希值,value:对应的结构体指针切片 var mapper = make(map[int][]*Profile) // 构建数据索引 //遍历传入的切片之中的数据,构建对应的查询索引结构体,计算对应的hash值 func buildIndex(list []*Profile) for _, profile := range list key := classicQueryKeyprofile.Name, profile.Age existValue := mapper[key.hash()] //相同哈希值的所有结构体切片,先拿出来 existValue = append(existValue, profile) //将对应的结构体指针添加到切片之中 mapper[key.hash()] = existValue //返回到对应的索引map之中 //查询函数 //构建查询键,获取相同哈希值的所有结果 //遍历得到的结果,并且与查询键进行对比 func queryData(name string, age int) keyToQuery := classicQueryKeyname, age resultList := mapper[keyToQuery.hash()] //获取哈希对应的结构体指针切片 for _, result := range resultList if result.Name == name && result.Age == age //进行对比 fmt.Println(result) return // 没有查询到时, 打印结果 fmt.Println("no found")
-
利用 map 特性的多键索引及查询
-
使用结构体进行多键索引和查询比传统的写法更为简单,最主要的区别是无须准备哈希函数及相应的字段无须做哈希合并
// 查询键 type queryKey struct Name string Age int // 创建查询键到数据的映射 //key:对应的查询键结构体,value:对应的结构体指针 var mapper = make(map[queryKey]*Profile) // 构建查询索引 func buildIndex(list []*Profile) for _, profile := range list key := queryKey Name: profile.Name, Age: profile.Age, mapper[key] = profile //查询逻辑 // 根据条件查询数据 func queryData(name string, age int) key := queryKeyname, age result, ok := mapper[key] if ok fmt.Println(result) else fmt.Println("no found")
-
-
-
代码量大大减少的关键是:Go语言的底层会为 map 的键自动构建哈希值。能够构建哈希值的类型必须是非动态类型、非指针、函数、闭包
- 非动态类型:可用数组,不能用切片
- 非指针:每个指针数值都不同,失去哈希意义
- 数、闭包不能作为 map 的键
sync.Map
-
Go语言中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的
-
sync.Map特性
-
无须初始化,直接声明即可
-
sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除
-
用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false
var dataMap sync.Map //将键值对存储至map之中 dataMap.Store("one",1) dataMap.Store("two",2) dataMap.Store("there",3) //读取值 fmt.Println(dataMap.Load("there")) //3 true //删除键值对 dataMap.Delete("one") //遍历 dataMap.Range(func(k,v interface ) bool fmt.Println(k,v) //two 2 there 3 return true )
-
-
需要注意点
- sync.Map 不能使用 make 创建
- sync.Map 将键和值以 interface 类型进行保存
- sync.Map 的 Delete 可以使用指定的键将对应的键值对删除
- Range() 方法可以遍历 sync.Map,遍历需要提供一个匿名函数,参数为 k、v,类型为 interface,每次 Range() 在遍历一个元素时,都会调用这个匿名函数把结果返回
- sync.Map 没有提供获取 map 数量的方法,替代方法是在获取 sync.Map 时遍历自行计算数量,sync.Map 为了保证并发安全有一些性能损失,因此在非并发情况下,使用 map 相比使用 sync.Map 会有更好的性能
以上是关于Go语言容器—Map的主要内容,如果未能解决你的问题,请参考以下文章
go语言学习笔记 — 基础 — 高级数据类型 — 数据容器 — 字典map:map键值对的增删改查