vault backup: 2026-06-07 11:08:10
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
---
|
||||
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可以让我们的代码更加简洁高效,但也要注意并发安全等问题。选择合适的数据结构和使用方式,才能写出更好的程序。
|
||||
Reference in New Issue
Block a user