8. Go语言Map
2025年2月14日大约 6 分钟Go语言Go语言golanggo基础语法map哈希表
Go语言Map
Map是Go语言中的一个重要的数据结构,它提供了键值对的存储方式,类似其他语言中的哈希表或字典。
Map的本质:"键值对储物间"
什么是Map?
可以把Map想象成一个特殊的储物间,每个物品(值)都有自己的标签(键)。通过这个标签,我们可以快速找到对应的物品。比如:一个存储学生成绩的系统,学号就是键,分数就是值。
Map的定义方式
在Go语言中,定义Map有几种常用方式:
// 1. 使用make函数创建 scoreMap := make(map[string]int) // 2. 创建时直接初始化 studentScores := map[string]int{ "张三": 95, "李四": 88, "王五": 92, } // 3. 声明一个空map var prices map[string]float64 // 注意:声明后需要通过make初始化才能使用 prices = make(map[string]float64)
Map的基本操作
package main import "fmt" func main() { // 创建一个存储水果价格的map fruitPrices := make(map[string]float64) // 添加键值对 fruitPrices["苹果"] = 5.5 fruitPrices["香蕉"] = 3.8 fruitPrices["橙子"] = 4.2 // 获取值 applePrice := fruitPrices["苹果"] fmt.Printf("苹果的价格是:%.2f元\n", applePrice) // 修改值 fruitPrices["苹果"] = 5.8 // 删除键值对 delete(fruitPrices, "香蕉") // 遍历map for fruit, price := range fruitPrices { fmt.Printf("%s的价格是:%.2f元\n", fruit, price) } }
运行结果:
苹果的价格是:5.50元 苹果的价格是:5.80元 橙子的价格是:4.20元
判断键是否存在
在Go语言中,访问map中不存在的键会返回该类型的零值。因此,我们需要一种方法来判断键是否真实存在:
package main import "fmt" func main() { userAge := map[string]int{ "Alice": 25, "Bob": 30, } // 使用两个变量接收返回值 age, exists := userAge["Tom"] if exists { fmt.Printf("Tom的年龄是:%d\n", age) } else { fmt.Println("Tom不在map中") } }
运行结果:
Tom不在map中
Map的进阶用法
1. 嵌套Map
Map的值可以是另一个Map,这样就形成了嵌套结构:
package main import "fmt" func main() { // 创建一个存储学生成绩的嵌套map studentScores := map[string]map[string]int{ "张三": { "数学": 95, "英语": 88, "语文": 92, }, "李四": { "数学": 90, "英语": 85, "语文": 88, }, } // 获取张三的英语成绩 englishScore := studentScores["张三"]["英语"] fmt.Printf("张三的英语成绩是:%d\n", englishScore) }
运行结果:
张三的英语成绩是:88
2. Map作为函数参数
Map是引用类型,作为函数参数时传递的是底层指针的拷贝:
func updatePrice(prices map[string]float64, fruit string, price float64) { prices[fruit] = price } func main() { fruitPrices := map[string]float64{ "苹果": 5.5, "香蕉": 3.8, } updatePrice(fruitPrices, "苹果", 6.0) fmt.Printf("更新后苹果的价格:%.2f\n", fruitPrices["苹果"]) }
使用技巧和注意事项
- 初始化容量
// 如果知道map大约需要存储多少键值对,可以在创建时指定容量 userAges := make(map[string]int, 100)
- 并发安全
// map不是并发安全的,需要使用sync.Map或互斥锁来保证并发安全 import "sync" var mutex sync.Mutex mutex.Lock() map操作 mutex.Unlock()
- 常见陷阱
- map的零值是nil,需要先初始化才能使用
- 不能对map的元素取地址
- map的遍历顺序是随机的,不要依赖遍历顺序
1. Map的随机遍历
package main import "fmt" func main() { // 创建一个简单的map m := map[string]int{ "a": 1, "b": 2, "c": 3, "d": 4, } // 连续遍历三次,观察输出顺序 for i := 0; i < 3; i++ { fmt.Printf("第%d次遍历:\n", i+1) for k, v := range m { fmt.Printf("key: %s, value: %d\n", k, v) } fmt.Println() } }
运行结果:
第1次遍历: key: a, value: 1 key: d, value: 4 key: b, value: 2 key: c, value: 3 第2次遍历: key: c, value: 3 key: a, value: 1 key: d, value: 4 key: b, value: 2 第3次遍历: key: b, value: 2 key: c, value: 3 key: d, value: 4 key: a, value: 1
从输出可以看到,每次遍历的顺序都是不同的,这就是map遍历的随机性。
2. Map的并发安全问题
这个部分涉及到并发编程,将在后续章节中详细介绍,这里只做简单介绍,如果不是很理解的话,可以等后面学习了并发编程之后再回来看。
package main import ( "fmt" "sync" "time" ) func main() { // 不安全的map操作 unsafeMap := make(map[int]int) // 使用WaitGroup等待所有goroutine完成 var wg sync.WaitGroup // 启动10个goroutine同时写map for i := 0; i < 10; i++ { wg.Add(1) go func(n int) { defer wg.Done() unsafeMap[n] = n // 可能导致panic time.Sleep(time.Millisecond) }(i) } // 安全的map操作 var mutex sync.Mutex safeMap := make(map[int]int) // 再次启动10个goroutine,这次使用互斥锁保护 for i := 0; i < 10; i++ { wg.Add(1) go func(n int) { defer wg.Done() mutex.Lock() safeMap[n] = n // 安全的写入 mutex.Unlock() time.Sleep(time.Millisecond) }(i) } wg.Wait() fmt.Printf("安全的map最终内容: %v\n", safeMap) }
运行结果:
fatal error: concurrent map writes // 第一个map可能会panic 安全的map最终内容: map[0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9] // 第二个map安全执行
或者使用sync.Map:
package main import ( "fmt" "sync" ) func main() { var sm sync.Map var wg sync.WaitGroup // 启动10个goroutine同时操作sync.Map for i := 0; i < 10; i++ { wg.Add(1) go func(n int) { defer wg.Done() sm.Store(n, n) // 存储键值对 }(i) } wg.Wait() // 遍历sync.Map sm.Range(func(key, value interface{}) bool { fmt.Printf("key: %v, value: %v\n", key, value) return true }) }
运行结果:
key: 5, value: 5 key: 0, value: 0 key: 1, value: 1 key: 2, value: 2 key: 3, value: 3 key: 4, value: 4 key: 6, value: 6 key: 7, value: 7 key: 8, value: 8 key: 9, value: 9
实际应用场景
- 缓存系统
cache := make(map[string]interface{})
- 计数器
wordCount := make(map[string]int) for _, word := range words { wordCount[word]++ }
- 去重
seen := make(map[string]bool) for _, item := range items { seen[item] = true }
小结
Map是Go语言中一个强大而灵活的数据结构,它提供了高效的键值对存储方案。在实际开发中,Map的应用非常广泛,从简单的数据存储到复杂的缓存系统都能见到它的身影。掌握Map的使用,对于Go程序开发来说是必不可少的。
记住:合理使用Map可以让我们的代码更加简洁高效,但也要注意并发安全等问题。选择合适的数据结构和使用方式,才能写出更好的程序。
关注秀才公众号:IT杨秀才,领取精品学习资料
- 公众号后台回复:Go面试,领取Go面试题库PDF
- 公众号后台回复:Go学习,领取Go必看书籍
- 公众号后台回复:大模型,领取大模型学习资料
- 公众号后台回复:111,领取架构学习资料
- 公众号后台回复:26届秋招,领取26届秋招企业汇总表
