Go 语言字典 (Map)
一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
截止目前, 星球 内专栏累计输出 66w+ 字,讲解图 2896+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2300+ 小伙伴加入学习 ,欢迎点击围观
很多场景下都需要字典容器,它描述的是一种映射关系,如字典中的页码对应相关页内容一样。
在 Go 语言 中提供的字典容器为 map
。 map
使用散列表(hash)实现。
一、初始化字典 map
Go 语言中定义字典 map 格式如下:
map [keyType]valueType
- keyType 表示键类型;
- valueType 表示键对应的值类型;
注意:键和键对应的值总是以一对一的形式存在。
下面是一段 map 的示例代码:
package main
import "fmt"
func main() {
// 定义一个键类型为字符串,值类型为整型的 map
m := make(map[string]int)
// 向 map 中添加一个键为 “犬小哈教程”,值为 1 的映射关系
key := "犬小哈教程"
m[key] = 1
// 输出 map 中键为 “犬小哈教程” 对应的值
fmt.Println(m[key])
// 尝试输出一个不能存在的键,会输出该值类型的默认值
n := m["犬小哈教程2"]
fmt.Println(n)
}
代码输出:
1
0
图示:
上面代码最后,我们尝试从 map 中获取一个并不存在的键(key), 此时会输出值类型的默认值,整型的默认值为 0 。
当我们需要明确知道 map 中是否存在某个键(key)时,可以使用下面这种写法:
// 声明一个 ok 变量,用来接收对应键是否存在于 map 中
value, ok := m["犬小哈教程2"]
// 如果值不存在,则输出值
if !ok {
fmt.Println(value)
}
1.1 字段 map 的另外一种初始化方式
字典 map 还存在另外一种初始化方式, 代码如下:
m := map[int](string){
1: "www",
2: "quanxiaoha",
3: "com",
}
上面的这段代码并没有使用 make()
, 而是通过大括号的方式来初始化字典 map, 有点像 JSON 格式一样,冒号左边的是键(key) , 右边的是值(value) ,键值对之间使用逗号分隔。
二、遍历字段 map
Go 语言中字典 map 的遍历需要使用 for range
循环,代码如下:
package main
import "fmt"
func main() {
m := map[int](string){
1: "www",
2: "quanxiaoha",
3: "com",
}
// 通过 for range 遍历, 获取 key, value 值并打印
for key, value := range m {
fmt.Printf("key: %d, value: %s\n", key, value)
}
}
代码输出如下:
key: 1, value: www
key: 2, value: quanxiaoha
key: 3, value: com
如果只需要遍历值,也可以通过 Go 语言变量 小节中说到的匿名变量来实现:
for _, value := range m {
fmt.Printf("value: %s\n", value)
}
只遍历键时,通过下面这种方式:
for key := range m {
fmt.Printf("key: %d\n", key)
}
注意: 字典 map 是一种无序的数据结构,不要期望输出时按照一定顺序输出。如果需要按顺序输出,请使用切片 来完成。
三、删除字典 map 中键值对
通过内置函数 delete()
来删除键值对,格式如下:
delete(map, 键)
- map 表示要删除的目标 map 对象;
- 键表示要删除的 map 中 key 键。
示例代码如下:
package main
import "fmt"
func main() {
m := map[int](string){
1: "www",
2: "quanxiaoha",
3: "com",
}
// 删除 map 中键为 1 的键值对
delete(m, 1)
// 通过 for range 遍历, 获取 key, value 值并打印
for key, value := range m {
fmt.Println(key, value)
}
}
代码输出如下:
2 quanxiaoha
3 com
四、能够在并发环境下使用的字典 sync.Map
Go 语言中的 map 在并发环境下,只读是线程安全的,同时读写是线程不安全的。
下面这段代码演示了并发环境下读写 map 会出现的问题,代码如下:
package main
func main() {
// 初始化一个键为整型,值也为整型的 map
m := make(map[int]int)
// 开启一段并发代码
go func() {
// 无限循环往 map 里写值
for {
m[1] = 1
}
}()
// 开启一段并发代码
go func() {
// 无限循环读取 map 数据
for {
_ = m[1]
}
}()
// 死循环,让上面的并发代码在后台执行
for {
}
}
运行上面的代码,会报错如下:
fatal error: concurrent map read and map write
错误提示:因为并发的对 map 进行读写。两个并发函数不断的对 map 进行读写发生了竞态问题。map 内部会对这种并发操作进行检查并提前发现。
正常情况下,针对并发读写的场景,是需要加锁处理的。但是加锁就意味了性能不高。Go 语言在 1.9 版本中提供了一种高效率的并发安全的 sync.Map
。
sync.Map
有以下特性:
- 无需初始化,直接声明即可;
sync.Map
不能使用 map 的方式进行取值、设置等操作,而是使用sync.Map
提供的方法进行调用,如:Store
表示存储,Load
表示获取,Delete
表示删除。- 针对遍历操作,需要使用
Range
配合一个回调函数,回调函数会返回内部遍历出来的值。Range
参数中的回调函数返回值功能是: 需要继续遍历时,返回true
;终止遍历时,返回false
。
示例代码如下:
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
// 添加一些键值对到 map 中
m.Store(1, "www")
m.Store(2, "quanxiaoha")
m.Store(3, "com")
// 从 sync.Map 中获取键为 2 的值
fmt.Println(m.Load(2))
// 删除键值对
m.Delete(1)
// 遍历 sync.Map 中的键值对
m.Range(func(key, value interface{}) bool {
fmt.Printf("key: %d, value: %s\n", key, value)
return true
})
}
代码输出如下:
quanxiaoha true
key: 2, value: quanxiaoha
key: 3, value: com
注意:
sync.Map
没有提供获取 map 元素数量的方法,你需要自行遍历计数。