8. Go语言Map
2025年2月14日大约 5 分钟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可以让我们的代码更加简洁高效,但也要注意并发安全等问题。选择合适的数据结构和使用方式,才能写出更好的程序。