在这里插入图片描述

📃个人主页:island1314

⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞

  • 生活总是不会一帆风顺,前进的道路也不会永远一马平川,如何面对挫折影响人生走向 – 《人民日报》


Map 映射

1. 简介

Map 是 Go 语言中的键值对(key-value)数据结构,也称为字典或哈希表。它提供了快速的键值查找能力。

基本语法

make(map[KeyType]ValueType, [cap])
  • 其中cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量
  • 但是 keyType 不能是 slice、map、function,因为这些无法用 == 判断
  • map 类型的变量默认初始值为nil
  • 注意:声明是不会分配内存的,需要使用 make() 函数来分配内存,分配内存后才能赋值和使用

map 声明示例

var a map[string]string
var a map[string]int
var a map[int]string
var a map[string] map[string]string

如果不分配内存,如下:

func main() {
	// map 声明 -- 使用千需要先 make
	var a map[string]string
	a["is"] = "land"
	fmt.Println(a)
}

// 输出
panic: assignment to entry in nil map

goroutine 1 [running]:
main.main()
        D:/C++Learning/go_learning/mapdemo/demo1/main.go:8 +0x2c
exit status 2

修改如下:

func main() {
	// map 声明 -- 使用千需要先 make
	var a map[string]string

	a = make(map[string]string, 10)

	a["is"] = "land"
	fmt.Println(a) // map[is:land]
}

map 使用方式

  1. 直接声明

    var cities map[string]string // 此时 map = nil
    cities = make(map[string]string, 10) // 分配 map 空间
    
  2. 直接 make

    var cities2 = make(map[string]string)
    
  3. 字面量声明并赋值

    var cities3 map[string]string = map[string]string{
        "no4" : "成都"
    }
    cities["no1"] = "北京"
    

示例

func main() {
    // 1. 使用 make 创建空 map
    var map1 map[string]int
    map1 = make(map[string]int)
    
    // 或者简写
    map2 := make(map[string]int)
    
    // 2. 字面量创建 map
    map3 := map[string]int{
        "张三": 25,
        "李四": 30,
        "王五": 28,
    }
    
    // 3. 创建空的字面量 map
    map4 := map[string]int{}
    
    // 4. 指定初始容量(可选优化)
    map5 := make(map[string]int, 100)  // 预分配容量
    
    fmt.Printf("map1: %v\n", map1)
    fmt.Printf("map2: %v\n", map2)
    fmt.Printf("map3: %v\n", map3)
    fmt.Printf("map4: %v\n", map4)
    fmt.Printf("map5: %v\n", map5)
    
    // 不同类型的 map
    stringMap := map[string]string{
        "name":    "张三",
        "city":    "北京",
        "country": "中国",
    }
    
    intMap := map[int]string{
        1: "一",
        2: "二",
        3: "三",
    }
    
    fmt.Printf("stringMap: %v\n", stringMap)
    fmt.Printf("intMap: %v\n", intMap)
}

2. Map 的基本操作

2.1 增、删、改、查操作
func main() {
    // 创建 map
    ages := make(map[string]int)
    
    // 1. 添加/修改元素(赋值操作)
    ages["张三"] = 25
    ages["李四"] = 30
    ages["王五"] = 28
    
    fmt.Println("添加元素后:", ages)
    
    // 2. 修改元素
    ages["张三"] = 26  // 修改张三的年龄
    fmt.Println("修改后:", ages)
    
    // 3. 查找元素
    age, exists := ages["李四"]
    if exists {
        fmt.Printf("李四的年龄: %d\n", age)
    } else {
        fmt.Println("未找到李四")
    }
    
    // 简化的查找方式(如果不需要区分零值和不存在)
    fmt.Printf("王五的年龄: %d\n", ages["王五"])
    
    // 4. 删除元素
    delete(ages, "王五")
    fmt.Println("删除王五后:", ages)
    
    // 删除不存在的键不会报错
    delete(ages, "不存在的人")
    fmt.Println("删除不存在的键后:", ages)
}
2.2 Map 的遍历

map 的遍历使用 for-range 的结构来遍历

func main() {
    scores := map[string]float64{
        "数学": 95.5,
        "英语": 87.0,
        "语文": 92.5,
        "物理": 88.0,
    }
    
    fmt.Println("原始 map:", scores)
    fmt.Println()
    
    // 1. 基本遍历(注意:顺序不固定)
    fmt.Println("基本遍历:")
    for subject, score := range scores {
        fmt.Printf("%s: %.1f分\n", subject, score)
    }
    fmt.Println()
    
    // 2. 只遍历键
    fmt.Println("只遍历键:")
    for subject := range scores {
        fmt.Printf("科目: %s\n", subject)
    }
    fmt.Println()
    
    // 3. 只遍历值
    fmt.Println("只遍历值:")
    for _, score := range scores {
        fmt.Printf("分数: %.1f\n", score)
    }
    fmt.Println()
    
    // 4. 按照特定顺序遍历(需要先排序键)
    fmt.Println("按字母顺序遍历:")
    keys := make([]string, 0, len(scores))
    for key := range scores {
        keys = append(keys, key)
    }
    
    // 对键进行排序
    // 注意:需要导入 sort 包
    fmt.Println("keys:", keys)
}
2.3 map 切片

基本介绍:切片的数据类型如果是map,则我们称为 slice of map,map切片,这样使用则map个数就可以动态变化了,

演示:使用一个 map 来记录 monster 信息,相当于一个 monster 对应一个 map,并且 monster 数量可以动态增加

func main() {
	monsters := make([]map[string]string, 2) // 开辟了两个大小
	if monsters[0] == nil {
		monsters[0] = make(map[string]string)
		monsters[0]["name"] = "猪"
		monsters[0]["age"] = "88"
	}
	if monsters[1] == nil {
		monsters[1] = make(map[string]string)
		monsters[1]["name"] = "猴子"
		monsters[1]["age"] = "18"
	}
	// 动态增加, 使用 append, 像之前那样增加会报错
	newMonster := map[string]string{
		"name": "兔子",
		"age":  "89",
	}
	monsters = append(monsters, newMonster)
	fmt.Println(monsters)
}
2.4 map 排序

Map 的遍历顺序是随机的(没有一个专门的方法针对map的key进行排序)

Go 在设计时故意让 map 的遍历顺序随机化(从 Go 1 开始),目的是:

  • 防止开发者依赖遍历顺序写 bug
  • 避免某些算法在特定输入下性能退化

但是有时候在小数据量相同运行环境下,map 的输出看起来“稳定”,但这只是巧合。Go 的 map 实现基于哈希表,其内部存储顺序取决于:

  • 键的哈希值
  • 哈希表的桶结构
  • Go 运行时的随机种子(每次程序启动不同)

所以不能假设任何顺序

如果需要有序遍历,怎么办?

提取键,排序后再遍历

示例

func main() {
	m := map[string]int{
		"apple":  5,
		"banana": 3,
		"cherry": 8,
	}

	// 1. 提取所有键
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}

	// 2. 排序键
	sort.Strings(keys)

	// 3. 按排序后的键遍历
	for _, k := range keys {
		fmt.Println(k, m[k])
	}
}
  • Strings(a []string):把 a 排序为递增序列

Ints(a []int):把 a 排序为递增序列,如下:

func main() {
	mp := make(map[int]int, 10)
	mp[10] = 100
	mp[1] = 13
	mp[-1] = 3
	mp[2] = 90
	fmt.Println(mp)

	var ks []int
	for k, _ := range mp {
		ks = append(ks, k)
	}
	sort.Ints(ks)
	fmt.Println(ks)
}

// 输出
map[-1:3 1:13 2:90 10:100]
[-1 1 2 10]

2. Map 特性

① Map 的零值和 nil

func main() {
	// 1. nil map
	var nilMap map[string]int
	fmt.Printf("nilMap: %v, 是否为 nil: %t\n", nilMap, nilMap == nil) // map[] true

	// nil map 可以读取(返回零值)
	fmt.Printf("nilMap['key']: %d\n", nilMap["key"]) // 0

	// nil map 不能写入(会导致 panic)
	// nilMap["key"] = 1  // 这会 panic!

	// 2. 空 map
	emptyMap := make(map[string]int)
	fmt.Printf("emptyMap: %v, 是否为 nil: %t\n", emptyMap, emptyMap == nil) // map[] false

	// 空 map 可以正常读写
	emptyMap["key"] = 1
	fmt.Printf("emptyMap['key']: %d\n", emptyMap["key"]) 

	// 3. 检查键是否存在
	value, exists := emptyMap["不存在的键"] 
	if !exists {
		fmt.Println("键不存在,返回零值:", value)
	}
}

② Map 的键类型限制

func main() {
    // Map 的键必须是可比较的类型
    
    // ✅ 合法的键类型
    intMap := map[int]string{1: "一", 2: "二"}
    stringMap := map[string]int{"one": 1, "two": 2}
    boolMap := map[bool]string{true: "真", false: "假"}
    
    // 复合键类型
    pointMap := map[[2]int]string{[2]int{0, 0}: "原点"}
    
    fmt.Println("intMap:", intMap)
    fmt.Println("stringMap:", stringMap)
    fmt.Println("boolMap:", boolMap)
    fmt.Println("pointMap:", pointMap)
    
    // ❌ 非法的键类型(编译错误)
    // sliceMap := map[[]int]string{}     // 错误:slice 不可比较
    // mapMap := map[map[string]int]string{} // 错误:map 不可比较
    // funcMap := map[func()]string{}     // 错误:函数不可比较
}

3. Map 嵌套和高级操作

嵌套 Map 和复杂数据结构

func main() {
    // 1. 嵌套 Map
    studentGrades := map[string]map[string]float64{
        "张三": {
            "数学": 95.5,
            "英语": 87.0,
            "语文": 92.5,
        },
        "李四": {
            "数学": 88.0,
            "英语": 91.5,
            "语文": 89.0,
        },
    }
    
    fmt.Println("嵌套 Map:")
    for student, grades := range studentGrades {
        fmt.Printf("%s 的成绩:\n", student)
        for subject, score := range grades {
            fmt.Printf("  %s: %.1f\n", subject, score)
        }
    }
    
    // 访问嵌套 Map
    zhangsanMath := studentGrades["张三"]["数学"]
    fmt.Printf("\n张三的数学成绩: %.1f\n", zhangsanMath)
    
    // 2. Map + Slice 组合
    classStudents := map[string][]string{
        "一班": {"张三", "李四", "王五"},
        "二班": {"赵六", "钱七", "孙八"},
    }
    
    fmt.Println("\n班级学生:")
    for class, students := range classStudents {
        fmt.Printf("%s: %v\n", class, students)
    }
}

Map 函数

// 统计字符出现次数
func countChars(s string) map[rune]int {
    counts := make(map[rune]int)
    for _, char := range s {
        counts[char]++
    }
    return counts
}

// 查找最大值对应的键
func findMaxKey(m map[string]int) (string, int) {
    if len(m) == 0 {
        return "", 0
    }
    
    maxKey := ""
    maxValue := 0
    for key, value := range m {
        if value > maxValue {
            maxValue = value
            maxKey = key
        }
    }
    return maxKey, maxValue
}

func main() {
    // 字符统计示例
    text := "hello world"
    charCounts := countChars(text)
    fmt.Printf("'%s' 中字符统计: %v\n", text, charCounts)
    
    // 查找最大值
    scores := map[string]int{
        "张三": 95,
        "李四": 87,
        "王五": 92,
        "赵六": 89,
    }
    
    bestStudent, highestScore := findMaxKey(scores)
    fmt.Printf("最高分: %s 得分 %d\n", bestStudent, highestScore)
}

4. 并发安全

① Map 不是并发安全

func main() {
    regularMap := make(map[int]int) // 普通 map 在并发访问时会有问题
    var wg sync.WaitGroup // 并发读写会导致 panic
    
    // 启动多个 goroutine 同时写入
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            for j := 0; j < 100; j++ {
                regularMap[id*100+j] = id
            }
        }(i)
    }
    
    // 启动多个 goroutine 同时读取
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := 0; j < 100; j++ {
                _ = regularMap[j]
            }
        }()
    }
    
    wg.Wait()
    fmt.Println("普通 map 并发操作完成(可能 panic)")
}

② sync.Map 并发安全的 Map

func main() {
    // sync.Map 是并发安全的
    var safeMap sync.Map
    
    // 存储值
    safeMap.Store("key1", "value1")
    safeMap.Store("key2", "value2")
    
    // 读取值
    if value, ok := safeMap.Load("key1"); ok {
        fmt.Println("key1:", value)
    }
    
    // 删除值
    safeMap.Delete("key1")
    
    // 遍历
    fmt.Println("遍历 sync.Map:")
    safeMap.Range(func(key, value interface{}) bool {
        fmt.Printf("%v: %v\n", key, value)
        return true  // 返回 true 继续遍历,false 停止
    })
    
    // 原子操作
    if actual, loaded := safeMap.LoadOrStore("key3", "value3"); loaded {
        fmt.Println("key3 已存在,值为:", actual)
    } else {
        fmt.Println("key3 不存在,已存储新值")
    }
}

5. 性能优化

① 预分配容量优化

func main() {
    // 测试预分配容量的性能差异
    const size = 100000
    
    // 不预分配容量
    start := time.Now()
    map1 := make(map[int]int)
    for i := 0; i < size; i++ {
        map1[i] = i * 2
    }
    duration1 := time.Since(start)
    
    // 预分配容量
    start = time.Now()
    map2 := make(map[int]int, size)  // 预分配容量
    for i := 0; i < size; i++ {
        map2[i] = i * 2
    }
    duration2 := time.Since(start)
    
    fmt.Printf("不预分配耗时: %v\n", duration1)
    fmt.Printf("预分配耗时: %v\n", duration2)
    fmt.Printf("性能提升: %.2f%%\n", 
        float64(duration1-duration2)/float64(duration1)*100)
}

② 内存使用优化

func main() {
    // 1. 及时删除不需要的键值对
    cache := make(map[string]string)
    
    // 模拟缓存使用
    for i := 0; i < 1000; i++ {
        key := fmt.Sprintf("key_%d", i)
        cache[key] = fmt.Sprintf("value_%d", i)
    }
    
    fmt.Printf("缓存大小: %d\n", len(cache))
    
    // 清理旧缓存
    for key := range cache {
        if len(key) > 10 {
            delete(cache, key)
        }
    }
    
    fmt.Printf("清理后大小: %d\n", len(cache))
    
    // 2. 重置 map(完全清空)
    // 方法1:重新创建
    cache = make(map[string]string)
    
    // 方法2:逐个删除(不推荐)
    // for key := range cache {
    //     delete(cache, key)
    // }
}

6. 使用细节

  1. map是引用类型,遵守引用类型传递的机制在一个函数接收map,修改后,会直接修改原来的map
  2. map的容量达到后,再向map增加元素,会自动扩容,并不会发生panic,也就是说map 能 动态增长键值对(key-value)
  3. map的value 也经常使用struct 类型,更适合管理复杂的数据(比前面value是一个map更好),比如value为 Student结构体
func modify(mp map[int]int) {
	mp[10] = 92
}

type Stu struct {
	Name string
	Age  int
}

func main() {
	// 1. map 是引用类型
	mp1 := make(map[int]int)
	mp1[1] = 90
	mp1[10] = 1
	modify(mp1)
	fmt.Println(mp1)

	// 2. 自动扩容
	mp2 := make(map[int]int, 2) // 虽然指定为 2 个大小, 但是会自动扩容 不报错
	mp2[1] = 66
	mp2[2] = 27
	mp2[10] = 1
	fmt.Println(mp2)

	// 3. map 的 value 对应 struct
	students := make(map[string]Stu)
	stu1 := Stu{"zs", 18}
	stu2 := Stu{Name: "tom", Age: 28}
	students["1001"] = stu1
	students["1002"] = stu2
	for i, v := range students {
		fmt.Printf("%v: %v\n", i, v)
	}
}

// 输出
map[1:90 10:92]
map[1:66 2:27 10:1]
1001: {zs 18}
1002: {tom 28}

注意事项

  1. 不要使用 nil Map
    未初始化的 Map 是 nil,不能对其进行添加、修改等操作,否则会引发运行时错误。在使用 Map 之前,一定要确保已经对其进行了初始化。
  2. 键的唯一性
    Map 中的键是唯一的,当添加元素时,如果使用的键已经存在,新的值会覆盖原来的值。因此,在使用 Map 时,需要确保键的唯一性。
  3. 并发安全问题
    • Map 不是并发安全的,在多个 goroutine 同时对 Map 进行读写操作时,会引发数据竞争问题。
    • 如果需要在并发环境中使用 Map,可以使用 sync.Map,它是 Go 语言标准库中提供的并发安全的 Map。
  4. 性能优化
    • 初始化 Map 时指定合适的初始容量,可以减少 Map 扩容的次数,提高性能。
    • 尽量使用简单的类型作为键,因为复杂类型的哈希计算和比较操作会更耗时。
    • 避免频繁地对 Map 进行删除和添加操作,这可能会导致哈希表的重组,影响性能。

7. 实践学习

学了这么多,还是得有一些实践的,要求如下:

  1. 使用 map[string]map[string]sting的map类型
  2. key:表示用户名,是唯一的,不可以重复
  3. 如果某个用户名存在,就将其密码修改"888888",如果不存在就增加这个用户信息(包括昵称nickmame和密码pwd)。
  4. 编写一个函数 modifyUser(users map[string]map[string]string,name string) 完成上述功能
func modifyUser(users map[string]map[string]string, name string) {
	// 判断 users 是否有 name
	// v, ok := users[name]
	if users[name] != nil {
		users[name]["pwd"] = "888888"
	} else {
		// 不存在
		users[name] = make(map[string]string, 2)
		users[name]["pwd"] = "888888"
		users[name]["nickname"] = "昵称~" + name
	}
}

func main() {
	users := make(map[string]map[string]string)
	modifyUser(users, "tom")
	modifyUser(users, "Mary")
	fmt.Println(users)
}

// 输出
map[Mary:map[nickname:昵称~Mary pwd:888888] tom:map[nickname:昵称~tom pwd:888888]]

★,°:.☆( ̄▽ ̄)/$:.°★ 】那么本篇到此就结束啦,如果有不懂 和 发现问题的小伙伴可以在评论区说出来哦,同时我还会继续更新关于【GoLang】的内容,请持续关注我 !!

在这里插入图片描述

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐