Files
golang/golang-learning/04-data-structures/03-maps.go
2025-08-24 01:15:18 +08:00

722 lines
17 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
03-maps.go - Go 语言映射详解
学习目标:
1. 理解映射的概念和特性
2. 掌握映射的创建和初始化
3. 学会映射的基本操作
4. 了解映射的内部实现
5. 掌握映射的实际应用场景
知识点:
- 映射的定义和特性
- 映射的创建方式
- 映射的增删改查操作
- 映射的遍历
- 映射的零值和比较
- 映射作为集合使用
- 映射的并发安全问题
*/
package main
import (
"fmt"
)
func main() {
fmt.Println("=== Go 语言映射详解 ===\n")
// 演示映射的基本概念
demonstrateBasicMaps()
// 演示映射的创建方式
demonstrateMapCreation()
// 演示映射的基本操作
demonstrateMapOperations()
// 演示映射的遍历
demonstrateMapIteration()
// 演示映射的高级操作
demonstrateAdvancedMapOperations()
// 演示映射作为集合使用
demonstrateMapAsSet()
// 演示映射的实际应用
demonstratePracticalApplications()
}
// demonstrateBasicMaps 演示映射的基本概念
func demonstrateBasicMaps() {
fmt.Println("1. 映射的基本概念:")
// 映射的基本特性
fmt.Printf(" 映射的基本特性:\n")
fmt.Printf(" - 键值对的无序集合\n")
fmt.Printf(" - 键必须是可比较的类型\n")
fmt.Printf(" - 引用类型,零值是 nil\n")
fmt.Printf(" - 动态大小,可以增长和缩减\n")
fmt.Printf(" - 不能直接比较,只能与 nil 比较\n")
// 基本映射示例
fmt.Printf(" 基本映射示例:\n")
var m map[string]int // 声明映射,零值为 nil
fmt.Printf(" 声明映射: %v\n", m)
fmt.Printf(" 是否为 nil: %t\n", m == nil)
fmt.Printf(" 长度: %d\n", len(m))
// 初始化映射
scores := map[string]int{
"Alice": 95,
"Bob": 87,
"Charlie": 92,
}
fmt.Printf(" 初始化映射: %v\n", scores)
fmt.Printf(" 长度: %d\n", len(scores))
// 访问映射元素
fmt.Printf(" Alice的分数: %d\n", scores["Alice"])
fmt.Printf(" 不存在的键: %d\n", scores["David"]) // 返回零值
fmt.Println()
}
// demonstrateMapCreation 演示映射的创建方式
func demonstrateMapCreation() {
fmt.Println("2. 映射的创建方式:")
// 方式1: 字面量创建
fmt.Printf(" 方式1 - 字面量创建:\n")
colors := map[string]string{
"red": "#FF0000",
"green": "#00FF00",
"blue": "#0000FF",
}
fmt.Printf(" colors: %v\n", colors)
// 方式2: make 函数创建
fmt.Printf(" 方式2 - make 函数创建:\n")
ages := make(map[string]int)
ages["Alice"] = 25
ages["Bob"] = 30
fmt.Printf(" ages: %v\n", ages)
// 方式3: 空映射字面量
fmt.Printf(" 方式3 - 空映射字面量:\n")
empty := map[string]int{}
fmt.Printf(" empty: %v (len=%d)\n", empty, len(empty))
fmt.Printf(" 是否为 nil: %t\n", empty == nil)
// 方式4: 带容量提示的 make
fmt.Printf(" 方式4 - 带容量提示的 make:\n")
large := make(map[string]int, 100) // 容量提示,不是限制
fmt.Printf(" large: %v (len=%d)\n", large, len(large))
// 不同类型的映射
fmt.Printf(" 不同类型的映射:\n")
// 整数键
intMap := map[int]string{1: "one", 2: "two", 3: "three"}
fmt.Printf(" 整数键: %v\n", intMap)
// 结构体值
type Person struct {
Name string
Age int
}
people := map[string]Person{
"p1": {"Alice", 25},
"p2": {"Bob", 30},
}
fmt.Printf(" 结构体值: %v\n", people)
// 切片值
groups := map[string][]string{
"fruits": {"apple", "banana", "cherry"},
"vegetables": {"carrot", "broccoli", "spinach"},
}
fmt.Printf(" 切片值: %v\n", groups)
// 映射值
nested := map[string]map[string]int{
"math": {"Alice": 95, "Bob": 87},
"english": {"Alice": 92, "Bob": 89},
}
fmt.Printf(" 嵌套映射: %v\n", nested)
fmt.Println()
}
// demonstrateMapOperations 演示映射的基本操作
func demonstrateMapOperations() {
fmt.Println("3. 映射的基本操作:")
// 创建映射
inventory := make(map[string]int)
// 添加/更新元素
fmt.Printf(" 添加/更新元素:\n")
inventory["apples"] = 50
inventory["bananas"] = 30
inventory["oranges"] = 25
fmt.Printf(" 添加水果: %v\n", inventory)
// 更新现有元素
inventory["apples"] = 45
fmt.Printf(" 更新苹果数量: %v\n", inventory)
// 读取元素
fmt.Printf(" 读取元素:\n")
apples := inventory["apples"]
fmt.Printf(" 苹果数量: %d\n", apples)
// 检查键是否存在
fmt.Printf(" 检查键是否存在:\n")
value, exists := inventory["bananas"]
fmt.Printf(" bananas存在: %t, 值: %d\n", exists, value)
value, exists = inventory["grapes"]
fmt.Printf(" grapes存在: %t, 值: %d\n", exists, value)
// 删除元素
fmt.Printf(" 删除元素:\n")
fmt.Printf(" 删除前: %v\n", inventory)
delete(inventory, "oranges")
fmt.Printf(" 删除oranges后: %v\n", inventory)
// 删除不存在的键(安全操作)
delete(inventory, "grapes")
fmt.Printf(" 删除不存在的键后: %v\n", inventory)
// 批量操作
fmt.Printf(" 批量操作:\n")
newItems := map[string]int{
"pears": 20,
"grapes": 35,
"kiwis": 15,
}
// 批量添加
for key, value := range newItems {
inventory[key] = value
}
fmt.Printf(" 批量添加后: %v\n", inventory)
// 清空映射
fmt.Printf(" 清空映射:\n")
for key := range inventory {
delete(inventory, key)
}
fmt.Printf(" 清空后: %v (len=%d)\n", inventory, len(inventory))
fmt.Println()
}//
demonstrateMapIteration 演示映射的遍历
func demonstrateMapIteration() {
fmt.Println("4. 映射的遍历:")
students := map[string]int{
"Alice": 95,
"Bob": 87,
"Charlie": 92,
"David": 78,
"Eve": 89,
}
// 遍历键值对
fmt.Printf(" 遍历键值对:\n")
for name, score := range students {
fmt.Printf(" %s: %d\n", name, score)
}
// 只遍历键
fmt.Printf(" 只遍历键:\n")
for name := range students {
fmt.Printf(" 学生: %s\n", name)
}
// 只遍历值
fmt.Printf(" 只遍历值:\n")
for _, score := range students {
fmt.Printf(" 分数: %d\n", score)
}
// 注意:映射遍历顺序是随机的
fmt.Printf(" 注意: 映射遍历顺序是随机的\n")
fmt.Printf(" 多次遍历可能得到不同的顺序\n")
// 有序遍历
fmt.Printf(" 有序遍历(按键排序):\n")
// 收集所有键
var names []string
for name := range students {
names = append(names, name)
}
// 排序键
sort.Strings(names)
// 按排序后的键遍历
for _, name := range names {
fmt.Printf(" %s: %d\n", name, students[name])
}
// 按值排序遍历
fmt.Printf(" 按分数排序遍历:\n")
type StudentScore struct {
Name string
Score int
}
var studentList []StudentScore
for name, score := range students {
studentList = append(studentList, StudentScore{name, score})
}
// 按分数排序
sort.Slice(studentList, func(i, j int) bool {
return studentList[i].Score > studentList[j].Score
})
for _, student := range studentList {
fmt.Printf(" %s: %d\n", student.Name, student.Score)
}
fmt.Println()
}
// demonstrateAdvancedMapOperations 演示映射的高级操作
func demonstrateAdvancedMapOperations() {
fmt.Println("5. 映射的高级操作:")
// 映射合并
fmt.Printf(" 映射合并:\n")
map1 := map[string]int{"a": 1, "b": 2, "c": 3}
map2 := map[string]int{"c": 30, "d": 4, "e": 5}
fmt.Printf(" map1: %v\n", map1)
fmt.Printf(" map2: %v\n", map2)
// 合并 map2 到 map1
for key, value := range map2 {
map1[key] = value
}
fmt.Printf(" 合并后: %v\n", map1)
// 映射复制
fmt.Printf(" 映射复制:\n")
original := map[string]int{"x": 10, "y": 20, "z": 30}
copied := make(map[string]int)
for key, value := range original {
copied[key] = value
}
fmt.Printf(" 原映射: %v\n", original)
fmt.Printf(" 复制映射: %v\n", copied)
// 修改复制的映射不影响原映射
copied["x"] = 99
fmt.Printf(" 修改复制后:\n")
fmt.Printf(" 原映射: %v\n", original)
fmt.Printf(" 复制映射: %v\n", copied)
// 映射过滤
fmt.Printf(" 映射过滤:\n")
allScores := map[string]int{
"Alice": 95, "Bob": 67, "Charlie": 92,
"David": 45, "Eve": 89, "Frank": 78,
}
// 过滤出及格的学生
passing := make(map[string]int)
for name, score := range allScores {
if score >= 70 {
passing[name] = score
}
}
fmt.Printf(" 所有分数: %v\n", allScores)
fmt.Printf(" 及格学生: %v\n", passing)
// 映射转换
fmt.Printf(" 映射转换:\n")
temperatures := map[string]float64{
"北京": 25.5,
"上海": 28.3,
"广州": 32.1,
}
// 摄氏度转华氏度
fahrenheit := make(map[string]float64)
for city, celsius := range temperatures {
fahrenheit[city] = celsius*9/5 + 32
}
fmt.Printf(" 摄氏度: %v\n", temperatures)
fmt.Printf(" 华氏度: %v\n", fahrenheit)
// 反转映射(键值互换)
fmt.Printf(" 反转映射:\n")
codeToName := map[int]string{
1: "Alice",
2: "Bob",
3: "Charlie",
}
nameToCode := make(map[string]int)
for code, name := range codeToName {
nameToCode[name] = code
}
fmt.Printf(" 代码到姓名: %v\n", codeToName)
fmt.Printf(" 姓名到代码: %v\n", nameToCode)
fmt.Println()
}
// demonstrateMapAsSet 演示映射作为集合使用
func demonstrateMapAsSet() {
fmt.Println("6. 映射作为集合使用:")
fmt.Printf(" Go 没有内置的集合类型,可以用 map[T]bool 模拟\n")
// 创建集合
fmt.Printf(" 创建集合:\n")
fruits := make(map[string]bool)
// 添加元素
fruits["apple"] = true
fruits["banana"] = true
fruits["cherry"] = true
fmt.Printf(" 水果集合: %v\n", getKeys(fruits))
// 检查元素是否存在
fmt.Printf(" 检查元素存在:\n")
fmt.Printf(" apple 存在: %t\n", fruits["apple"])
fmt.Printf(" grape 存在: %t\n", fruits["grape"])
// 删除元素
delete(fruits, "banana")
fmt.Printf(" 删除 banana 后: %v\n", getKeys(fruits))
// 集合运算
fmt.Printf(" 集合运算:\n")
set1 := map[string]bool{"a": true, "b": true, "c": true}
set2 := map[string]bool{"b": true, "c": true, "d": true}
fmt.Printf(" 集合1: %v\n", getKeys(set1))
fmt.Printf(" 集合2: %v\n", getKeys(set2))
// 并集
union := make(map[string]bool)
for key := range set1 {
union[key] = true
}
for key := range set2 {
union[key] = true
}
fmt.Printf(" 并集: %v\n", getKeys(union))
// 交集
intersection := make(map[string]bool)
for key := range set1 {
if set2[key] {
intersection[key] = true
}
}
fmt.Printf(" 交集: %v\n", getKeys(intersection))
// 差集 (set1 - set2)
difference := make(map[string]bool)
for key := range set1 {
if !set2[key] {
difference[key] = true
}
}
fmt.Printf(" 差集(1-2): %v\n", getKeys(difference))
// 使用 map[T]struct{} 更节省内存
fmt.Printf(" 使用 struct{} 作为值更节省内存:\n")
efficientSet := make(map[string]struct{})
efficientSet["item1"] = struct{}{}
efficientSet["item2"] = struct{}{}
// 检查存在
_, exists := efficientSet["item1"]
fmt.Printf(" item1 存在: %t\n", exists)
fmt.Println()
}
// demonstratePracticalApplications 演示映射的实际应用
func demonstratePracticalApplications() {
fmt.Println("7. 映射的实际应用:")
// 应用1: 计数器
fmt.Printf(" 应用1 - 字符频率统计:\n")
text := "hello world"
frequency := make(map[rune]int)
for _, char := range text {
if char != ' ' { // 忽略空格
frequency[char]++
}
}
fmt.Printf(" 文本: \"%s\"\n", text)
fmt.Printf(" 字符频率:\n")
for char, count := range frequency {
fmt.Printf(" '%c': %d\n", char, count)
}
// 应用2: 缓存系统
fmt.Printf(" 应用2 - 简单缓存:\n")
cache := NewCache()
// 设置缓存
cache.Set("user:123", "Alice")
cache.Set("user:456", "Bob")
// 获取缓存
if value, found := cache.Get("user:123"); found {
fmt.Printf(" 缓存命中: user:123 = %s\n", value)
}
if value, found := cache.Get("user:789"); found {
fmt.Printf(" 缓存命中: user:789 = %s\n", value)
} else {
fmt.Printf(" 缓存未命中: user:789\n")
}
// 应用3: 配置管理
fmt.Printf(" 应用3 - 配置管理:\n")
config := map[string]interface{}{
"database.host": "localhost",
"database.port": 5432,
"database.ssl": true,
"app.debug": false,
"app.max_connections": 100,
}
fmt.Printf(" 配置项:\n")
for key, value := range config {
fmt.Printf(" %s: %v (%T)\n", key, value, value)
}
// 应用4: 分组统计
fmt.Printf(" 应用4 - 学生成绩分组:\n")
students := []struct {
Name string
Grade string
Score int
}{
{"Alice", "A", 95},
{"Bob", "B", 87},
{"Charlie", "A", 92},
{"David", "C", 78},
{"Eve", "B", 89},
{"Frank", "A", 96},
}
// 按等级分组
gradeGroups := make(map[string][]string)
gradeScores := make(map[string][]int)
for _, student := range students {
gradeGroups[student.Grade] = append(gradeGroups[student.Grade], student.Name)
gradeScores[student.Grade] = append(gradeScores[student.Grade], student.Score)
}
fmt.Printf(" 按等级分组:\n")
for grade, names := range gradeGroups {
scores := gradeScores[grade]
avg := average(scores)
fmt.Printf(" %s级: %v (平均分: %.1f)\n", grade, names, avg)
}
// 应用5: 索引构建
fmt.Printf(" 应用5 - 倒排索引:\n")
documents := map[string]string{
"doc1": "go programming language",
"doc2": "python programming tutorial",
"doc3": "go web development",
"doc4": "java programming guide",
}
// 构建倒排索引
index := buildInvertedIndex(documents)
fmt.Printf(" 文档:\n")
for docId, content := range documents {
fmt.Printf(" %s: %s\n", docId, content)
}
fmt.Printf(" 倒排索引:\n")
for word, docIds := range index {
fmt.Printf(" %s: %v\n", word, docIds)
}
// 搜索
searchWord := "programming"
if docIds, found := index[searchWord]; found {
fmt.Printf(" 搜索 '%s': 找到文档 %v\n", searchWord, docIds)
}
fmt.Println()
}
// ========== 辅助函数和类型 ==========
// 获取映射的所有键
func getKeys(m map[string]bool) []string {
var keys []string
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys) // 排序以便输出一致
return keys
}
// 计算平均值
func average(numbers []int) float64 {
if len(numbers) == 0 {
return 0
}
sum := 0
for _, num := range numbers {
sum += num
}
return float64(sum) / float64(len(numbers))
}
// 简单缓存实现
type Cache struct {
data map[string]string
}
func NewCache() *Cache {
return &Cache{
data: make(map[string]string),
}
}
func (c *Cache) Set(key, value string) {
c.data[key] = value
}
func (c *Cache) Get(key string) (string, bool) {
value, exists := c.data[key]
return value, exists
}
func (c *Cache) Delete(key string) {
delete(c.data, key)
}
func (c *Cache) Clear() {
c.data = make(map[string]string)
}
// 构建倒排索引
func buildInvertedIndex(documents map[string]string) map[string][]string {
index := make(map[string][]string)
for docId, content := range documents {
words := strings.Fields(content)
for _, word := range words {
// 检查是否已经包含该文档
found := false
for _, existingDocId := range index[word] {
if existingDocId == docId {
found = true
break
}
}
if !found {
index[word] = append(index[word], docId)
}
}
}
return index
}
// 需要导入 strings 包
import "strings"
/*
运行这个程序:
go run 03-maps.go
学习要点:
1. 映射是键值对的无序集合,键必须是可比较类型
2. 映射是引用类型,零值是 nil
3. 映射的长度是动态的,可以增长和缩减
4. 映射不能直接比较,只能与 nil 比较
5. 映射的遍历顺序是随机的
映射的特性:
1. 键唯一性:每个键只能对应一个值
2. 动态大小:可以动态添加和删除键值对
3. 快速查找:平均 O(1) 时间复杂度
4. 无序性:不保证遍历顺序
5. 引用语义:赋值和传参传递引用
创建映射的方式:
1. 字面量map[KeyType]ValueType{key: value}
2. make函数make(map[KeyType]ValueType)
3. 空字面量map[KeyType]ValueType{}
基本操作:
1. 添加/更新m[key] = value
2. 读取value := m[key]
3. 检查存在value, ok := m[key]
4. 删除delete(m, key)
5. 长度len(m)
可比较的键类型:
1. 基本类型bool, 数值类型, string
2. 指针类型
3. 数组类型(元素可比较)
4. 结构体类型(所有字段可比较)
不可比较的键类型:
1. 切片类型
2. 映射类型
3. 函数类型
4. 包含不可比较字段的结构体
常见应用场景:
1. 缓存和查找表
2. 计数器和统计
3. 配置管理
4. 索引构建
5. 集合操作
6. 分组和聚合
最佳实践:
1. 检查键是否存在避免零值混淆
2. 使用 make 创建空映射而不是 nil 映射
3. 大映射考虑预分配容量
4. 使用 map[T]struct{} 实现集合节省内存
5. 注意映射的并发安全问题
注意事项:
1. nil 映射不能写入,只能读取
2. 映射不是并发安全的
3. 映射的遍历顺序是随机的
4. 删除不存在的键是安全操作
5. 映射的零值是 nil不是空映射
*/