--- tags: - Go - golang - go基础语法 - map - 哈希表 --- # Go语言Map Map是Go语言中的一个重要的数据结构,它提供了键值对的存储方式,类似其他语言中的哈希表或字典。 ## Map的本质:"键值对储物间" ### 什么是Map? 可以把Map想象成一个特殊的储物间,每个物品(值)都有自己的标签(键)。通过这个标签,我们可以快速找到对应的物品。比如:一个存储学生成绩的系统,学号就是键,分数就是值。 ### Map的定义方式 在Go语言中,定义Map有几种常用方式: ```go // 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的基本操作 ```go 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中不存在的键会返回该类型的零值。因此,我们需要一种方法来判断键是否真实存在: ```go 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,这样就形成了嵌套结构: ```go 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是引用类型,作为函数参数时传递的是底层指针的拷贝: ```go 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["苹果"]) } ``` ## 使用技巧和注意事项 1. 初始化容量 ```go // 如果知道map大约需要存储多少键值对,可以在创建时指定容量 userAges := make(map[string]int, 100) ``` 2. 并发安全 ```go // map不是并发安全的,需要使用sync.Map或互斥锁来保证并发安全 import "sync" var mutex sync.Mutex mutex.Lock() map操作 mutex.Unlock() ``` 3. 常见陷阱 - map的零值是nil,需要先初始化才能使用 - 不能对map的元素取地址 - map的遍历顺序是随机的,不要依赖遍历顺序 ### 1. Map的随机遍历 ```go 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的并发安全问题 这个部分涉及到并发编程,将在后续章节中详细介绍,这里只做简单介绍,如果不是很理解的话,可以等后面学习了并发编程之后再回来看。 ```go 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: ```go 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 ``` ## 实际应用场景 1. 缓存系统 ```go cache := make(map[string]interface{}) ``` 2. 计数器 ```go wordCount := make(map[string]int) for _, word := range words { wordCount[word]++ } ``` 3. 去重 ```go seen := make(map[string]bool) for _, item := range items { seen[item] = true } ``` ## 小结 Map是Go语言中一个强大而灵活的数据结构,它提供了高效的键值对存储方案。在实际开发中,Map的应用非常广泛,从简单的数据存储到复杂的缓存系统都能见到它的身影。掌握Map的使用,对于Go程序开发来说是必不可少的。 记住:合理使用Map可以让我们的代码更加简洁高效,但也要注意并发安全等问题。选择合适的数据结构和使用方式,才能写出更好的程序。