【GoLang #6】:Map 映射

📃个人主页:island1314
⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞
- 生活总是不会一帆风顺,前进的道路也不会永远一马平川,如何面对挫折影响人生走向 – 《人民日报》
🔥 目录
Map 映射
1. 简介
Map 是 Go 语言中的键值对(key-value)数据结构,也称为字典或哈希表。它提供了快速的键值查找能力。
基本语法:
make(map[KeyType]ValueType, [cap])
- 其中cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量
- 但是 keyType 不能是 slice、map、function,因为这些无法用
==判断 - map 类型的变量默认初始值为nil
- 注意:声明是不会分配内存的,需要使用
make()函数来分配内存,分配内存后才能赋值和使用
map 声明示例
var a map[string]string
var a map[string]int
var a map[int]string
var a map[string] map[string]string
如果不分配内存,如下:
func main() {
// map 声明 -- 使用千需要先 make
var a map[string]string
a["is"] = "land"
fmt.Println(a)
}
// 输出
panic: assignment to entry in nil map
goroutine 1 [running]:
main.main()
D:/C++Learning/go_learning/mapdemo/demo1/main.go:8 +0x2c
exit status 2
修改如下:
func main() {
// map 声明 -- 使用千需要先 make
var a map[string]string
a = make(map[string]string, 10)
a["is"] = "land"
fmt.Println(a) // map[is:land]
}
map 使用方式
-
直接声明
var cities map[string]string // 此时 map = nil cities = make(map[string]string, 10) // 分配 map 空间 -
直接 make
var cities2 = make(map[string]string) -
字面量声明并赋值
var cities3 map[string]string = map[string]string{ "no4" : "成都" } cities["no1"] = "北京"
示例
func main() {
// 1. 使用 make 创建空 map
var map1 map[string]int
map1 = make(map[string]int)
// 或者简写
map2 := make(map[string]int)
// 2. 字面量创建 map
map3 := map[string]int{
"张三": 25,
"李四": 30,
"王五": 28,
}
// 3. 创建空的字面量 map
map4 := map[string]int{}
// 4. 指定初始容量(可选优化)
map5 := make(map[string]int, 100) // 预分配容量
fmt.Printf("map1: %v\n", map1)
fmt.Printf("map2: %v\n", map2)
fmt.Printf("map3: %v\n", map3)
fmt.Printf("map4: %v\n", map4)
fmt.Printf("map5: %v\n", map5)
// 不同类型的 map
stringMap := map[string]string{
"name": "张三",
"city": "北京",
"country": "中国",
}
intMap := map[int]string{
1: "一",
2: "二",
3: "三",
}
fmt.Printf("stringMap: %v\n", stringMap)
fmt.Printf("intMap: %v\n", intMap)
}
2. Map 的基本操作
2.1 增、删、改、查操作
func main() {
// 创建 map
ages := make(map[string]int)
// 1. 添加/修改元素(赋值操作)
ages["张三"] = 25
ages["李四"] = 30
ages["王五"] = 28
fmt.Println("添加元素后:", ages)
// 2. 修改元素
ages["张三"] = 26 // 修改张三的年龄
fmt.Println("修改后:", ages)
// 3. 查找元素
age, exists := ages["李四"]
if exists {
fmt.Printf("李四的年龄: %d\n", age)
} else {
fmt.Println("未找到李四")
}
// 简化的查找方式(如果不需要区分零值和不存在)
fmt.Printf("王五的年龄: %d\n", ages["王五"])
// 4. 删除元素
delete(ages, "王五")
fmt.Println("删除王五后:", ages)
// 删除不存在的键不会报错
delete(ages, "不存在的人")
fmt.Println("删除不存在的键后:", ages)
}
2.2 Map 的遍历
map 的遍历使用 for-range 的结构来遍历
func main() {
scores := map[string]float64{
"数学": 95.5,
"英语": 87.0,
"语文": 92.5,
"物理": 88.0,
}
fmt.Println("原始 map:", scores)
fmt.Println()
// 1. 基本遍历(注意:顺序不固定)
fmt.Println("基本遍历:")
for subject, score := range scores {
fmt.Printf("%s: %.1f分\n", subject, score)
}
fmt.Println()
// 2. 只遍历键
fmt.Println("只遍历键:")
for subject := range scores {
fmt.Printf("科目: %s\n", subject)
}
fmt.Println()
// 3. 只遍历值
fmt.Println("只遍历值:")
for _, score := range scores {
fmt.Printf("分数: %.1f\n", score)
}
fmt.Println()
// 4. 按照特定顺序遍历(需要先排序键)
fmt.Println("按字母顺序遍历:")
keys := make([]string, 0, len(scores))
for key := range scores {
keys = append(keys, key)
}
// 对键进行排序
// 注意:需要导入 sort 包
fmt.Println("keys:", keys)
}
2.3 map 切片
基本介绍:切片的数据类型如果是map,则我们称为 slice of map,map切片,这样使用则map个数就可以动态变化了,
演示:使用一个 map 来记录 monster 信息,相当于一个 monster 对应一个 map,并且 monster 数量可以动态增加
func main() {
monsters := make([]map[string]string, 2) // 开辟了两个大小
if monsters[0] == nil {
monsters[0] = make(map[string]string)
monsters[0]["name"] = "猪"
monsters[0]["age"] = "88"
}
if monsters[1] == nil {
monsters[1] = make(map[string]string)
monsters[1]["name"] = "猴子"
monsters[1]["age"] = "18"
}
// 动态增加, 使用 append, 像之前那样增加会报错
newMonster := map[string]string{
"name": "兔子",
"age": "89",
}
monsters = append(monsters, newMonster)
fmt.Println(monsters)
}
2.4 map 排序
Map 的遍历顺序是随机的(没有一个专门的方法针对map的key进行排序)
Go 在设计时故意让 map 的遍历顺序随机化(从 Go 1 开始),目的是:
- 防止开发者依赖遍历顺序写 bug
- 避免某些算法在特定输入下性能退化
但是有时候在小数据量或相同运行环境下,map 的输出看起来“稳定”,但这只是巧合。Go 的 map 实现基于哈希表,其内部存储顺序取决于:
- 键的哈希值
- 哈希表的桶结构
- Go 运行时的随机种子(每次程序启动不同)
所以不能假设任何顺序。
如果需要有序遍历,怎么办?
提取键,排序后再遍历
示例
func main() {
m := map[string]int{
"apple": 5,
"banana": 3,
"cherry": 8,
}
// 1. 提取所有键
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
// 2. 排序键
sort.Strings(keys)
// 3. 按排序后的键遍历
for _, k := range keys {
fmt.Println(k, m[k])
}
}
Strings(a []string):把 a 排序为递增序列
Ints(a []int):把 a 排序为递增序列,如下:
func main() {
mp := make(map[int]int, 10)
mp[10] = 100
mp[1] = 13
mp[-1] = 3
mp[2] = 90
fmt.Println(mp)
var ks []int
for k, _ := range mp {
ks = append(ks, k)
}
sort.Ints(ks)
fmt.Println(ks)
}
// 输出
map[-1:3 1:13 2:90 10:100]
[-1 1 2 10]
2. Map 特性
① Map 的零值和 nil
func main() {
// 1. nil map
var nilMap map[string]int
fmt.Printf("nilMap: %v, 是否为 nil: %t\n", nilMap, nilMap == nil) // map[] true
// nil map 可以读取(返回零值)
fmt.Printf("nilMap['key']: %d\n", nilMap["key"]) // 0
// nil map 不能写入(会导致 panic)
// nilMap["key"] = 1 // 这会 panic!
// 2. 空 map
emptyMap := make(map[string]int)
fmt.Printf("emptyMap: %v, 是否为 nil: %t\n", emptyMap, emptyMap == nil) // map[] false
// 空 map 可以正常读写
emptyMap["key"] = 1
fmt.Printf("emptyMap['key']: %d\n", emptyMap["key"])
// 3. 检查键是否存在
value, exists := emptyMap["不存在的键"]
if !exists {
fmt.Println("键不存在,返回零值:", value)
}
}
② Map 的键类型限制
func main() {
// Map 的键必须是可比较的类型
// ✅ 合法的键类型
intMap := map[int]string{1: "一", 2: "二"}
stringMap := map[string]int{"one": 1, "two": 2}
boolMap := map[bool]string{true: "真", false: "假"}
// 复合键类型
pointMap := map[[2]int]string{[2]int{0, 0}: "原点"}
fmt.Println("intMap:", intMap)
fmt.Println("stringMap:", stringMap)
fmt.Println("boolMap:", boolMap)
fmt.Println("pointMap:", pointMap)
// ❌ 非法的键类型(编译错误)
// sliceMap := map[[]int]string{} // 错误:slice 不可比较
// mapMap := map[map[string]int]string{} // 错误:map 不可比较
// funcMap := map[func()]string{} // 错误:函数不可比较
}
3. Map 嵌套和高级操作
嵌套 Map 和复杂数据结构
func main() {
// 1. 嵌套 Map
studentGrades := map[string]map[string]float64{
"张三": {
"数学": 95.5,
"英语": 87.0,
"语文": 92.5,
},
"李四": {
"数学": 88.0,
"英语": 91.5,
"语文": 89.0,
},
}
fmt.Println("嵌套 Map:")
for student, grades := range studentGrades {
fmt.Printf("%s 的成绩:\n", student)
for subject, score := range grades {
fmt.Printf(" %s: %.1f\n", subject, score)
}
}
// 访问嵌套 Map
zhangsanMath := studentGrades["张三"]["数学"]
fmt.Printf("\n张三的数学成绩: %.1f\n", zhangsanMath)
// 2. Map + Slice 组合
classStudents := map[string][]string{
"一班": {"张三", "李四", "王五"},
"二班": {"赵六", "钱七", "孙八"},
}
fmt.Println("\n班级学生:")
for class, students := range classStudents {
fmt.Printf("%s: %v\n", class, students)
}
}
Map 函数
// 统计字符出现次数
func countChars(s string) map[rune]int {
counts := make(map[rune]int)
for _, char := range s {
counts[char]++
}
return counts
}
// 查找最大值对应的键
func findMaxKey(m map[string]int) (string, int) {
if len(m) == 0 {
return "", 0
}
maxKey := ""
maxValue := 0
for key, value := range m {
if value > maxValue {
maxValue = value
maxKey = key
}
}
return maxKey, maxValue
}
func main() {
// 字符统计示例
text := "hello world"
charCounts := countChars(text)
fmt.Printf("'%s' 中字符统计: %v\n", text, charCounts)
// 查找最大值
scores := map[string]int{
"张三": 95,
"李四": 87,
"王五": 92,
"赵六": 89,
}
bestStudent, highestScore := findMaxKey(scores)
fmt.Printf("最高分: %s 得分 %d\n", bestStudent, highestScore)
}
4. 并发安全
① Map 不是并发安全
func main() {
regularMap := make(map[int]int) // 普通 map 在并发访问时会有问题
var wg sync.WaitGroup // 并发读写会导致 panic
// 启动多个 goroutine 同时写入
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 100; j++ {
regularMap[id*100+j] = id
}
}(i)
}
// 启动多个 goroutine 同时读取
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
_ = regularMap[j]
}
}()
}
wg.Wait()
fmt.Println("普通 map 并发操作完成(可能 panic)")
}
② sync.Map 并发安全的 Map
func main() {
// sync.Map 是并发安全的
var safeMap sync.Map
// 存储值
safeMap.Store("key1", "value1")
safeMap.Store("key2", "value2")
// 读取值
if value, ok := safeMap.Load("key1"); ok {
fmt.Println("key1:", value)
}
// 删除值
safeMap.Delete("key1")
// 遍历
fmt.Println("遍历 sync.Map:")
safeMap.Range(func(key, value interface{}) bool {
fmt.Printf("%v: %v\n", key, value)
return true // 返回 true 继续遍历,false 停止
})
// 原子操作
if actual, loaded := safeMap.LoadOrStore("key3", "value3"); loaded {
fmt.Println("key3 已存在,值为:", actual)
} else {
fmt.Println("key3 不存在,已存储新值")
}
}
5. 性能优化
① 预分配容量优化
func main() {
// 测试预分配容量的性能差异
const size = 100000
// 不预分配容量
start := time.Now()
map1 := make(map[int]int)
for i := 0; i < size; i++ {
map1[i] = i * 2
}
duration1 := time.Since(start)
// 预分配容量
start = time.Now()
map2 := make(map[int]int, size) // 预分配容量
for i := 0; i < size; i++ {
map2[i] = i * 2
}
duration2 := time.Since(start)
fmt.Printf("不预分配耗时: %v\n", duration1)
fmt.Printf("预分配耗时: %v\n", duration2)
fmt.Printf("性能提升: %.2f%%\n",
float64(duration1-duration2)/float64(duration1)*100)
}
② 内存使用优化
func main() {
// 1. 及时删除不需要的键值对
cache := make(map[string]string)
// 模拟缓存使用
for i := 0; i < 1000; i++ {
key := fmt.Sprintf("key_%d", i)
cache[key] = fmt.Sprintf("value_%d", i)
}
fmt.Printf("缓存大小: %d\n", len(cache))
// 清理旧缓存
for key := range cache {
if len(key) > 10 {
delete(cache, key)
}
}
fmt.Printf("清理后大小: %d\n", len(cache))
// 2. 重置 map(完全清空)
// 方法1:重新创建
cache = make(map[string]string)
// 方法2:逐个删除(不推荐)
// for key := range cache {
// delete(cache, key)
// }
}
6. 使用细节
- map是引用类型,遵守引用类型传递的机制在一个函数接收map,修改后,会直接修改原来的map
- map的容量达到后,再向map增加元素,会自动扩容,并不会发生panic,也就是说map 能 动态增长键值对(key-value)
- map的value 也经常使用struct 类型,更适合管理复杂的数据(比前面value是一个map更好),比如value为 Student结构体
func modify(mp map[int]int) {
mp[10] = 92
}
type Stu struct {
Name string
Age int
}
func main() {
// 1. map 是引用类型
mp1 := make(map[int]int)
mp1[1] = 90
mp1[10] = 1
modify(mp1)
fmt.Println(mp1)
// 2. 自动扩容
mp2 := make(map[int]int, 2) // 虽然指定为 2 个大小, 但是会自动扩容 不报错
mp2[1] = 66
mp2[2] = 27
mp2[10] = 1
fmt.Println(mp2)
// 3. map 的 value 对应 struct
students := make(map[string]Stu)
stu1 := Stu{"zs", 18}
stu2 := Stu{Name: "tom", Age: 28}
students["1001"] = stu1
students["1002"] = stu2
for i, v := range students {
fmt.Printf("%v: %v\n", i, v)
}
}
// 输出
map[1:90 10:92]
map[1:66 2:27 10:1]
1001: {zs 18}
1002: {tom 28}
注意事项
- 不要使用 nil Map
未初始化的 Map 是 nil,不能对其进行添加、修改等操作,否则会引发运行时错误。在使用 Map 之前,一定要确保已经对其进行了初始化。 - 键的唯一性
Map 中的键是唯一的,当添加元素时,如果使用的键已经存在,新的值会覆盖原来的值。因此,在使用 Map 时,需要确保键的唯一性。 - 并发安全问题
- Map 不是并发安全的,在多个 goroutine 同时对 Map 进行读写操作时,会引发数据竞争问题。
- 如果需要在并发环境中使用 Map,可以使用 sync.Map,它是 Go 语言标准库中提供的并发安全的 Map。
- 性能优化
- 初始化 Map 时指定合适的初始容量,可以减少 Map 扩容的次数,提高性能。
- 尽量使用简单的类型作为键,因为复杂类型的哈希计算和比较操作会更耗时。
- 避免频繁地对 Map 进行删除和添加操作,这可能会导致哈希表的重组,影响性能。
7. 实践学习
学了这么多,还是得有一些实践的,要求如下:
- 使用 map[string]map[string]sting的map类型
- key:表示用户名,是唯一的,不可以重复
- 如果某个用户名存在,就将其密码修改"888888",如果不存在就增加这个用户信息(包括昵称nickmame和密码pwd)。
- 编写一个函数
modifyUser(users map[string]map[string]string,name string)完成上述功能
func modifyUser(users map[string]map[string]string, name string) {
// 判断 users 是否有 name
// v, ok := users[name]
if users[name] != nil {
users[name]["pwd"] = "888888"
} else {
// 不存在
users[name] = make(map[string]string, 2)
users[name]["pwd"] = "888888"
users[name]["nickname"] = "昵称~" + name
}
}
func main() {
users := make(map[string]map[string]string)
modifyUser(users, "tom")
modifyUser(users, "Mary")
fmt.Println(users)
}
// 输出
map[Mary:map[nickname:昵称~Mary pwd:888888] tom:map[nickname:昵称~tom pwd:888888]]
【★,°:.☆( ̄▽ ̄)/$:.°★ 】那么本篇到此就结束啦,如果有不懂 和 发现问题的小伙伴可以在评论区说出来哦,同时我还会继续更新关于【GoLang】的内容,请持续关注我 !!

更多推荐



所有评论(0)