Go语言快速入门Go语言快速入门
首页
基础篇
进阶篇
高阶篇
实战篇
Go官方网站
编程指南
首页
基础篇
进阶篇
高阶篇
实战篇
Go官方网站
编程指南
  • 基础篇

    • 🚀 基础篇
    • 第1章 - 环境安装
    • 第2章 - Hello World
    • 第3章 - 变量与常量
    • 第4章 - 数据类型
    • 控制流程
    • 函数
    • 数组与切片
    • Map
    • 结构体
    • 指针

Map

Map是键值对的集合,类似Java的HashMap或JavaScript的Object/Map。我学Go的时候,最容易踩的坑就是nil map和空map的区别。记住,永远用make或字面量创建map,不要用var声明后直接用!

创建Map

// 声明(nil map,不能直接使用)
var m1 map[string]int

// 使用make创建(推荐!)
m2 := make(map[string]int)

// 字面量创建
m3 := map[string]int{
    "apple":  5,
    "banana": 3,
    "orange": 8,
}

// 空map
m4 := map[string]int{}

nil map vs 空 map

var m1 map[string]int      // nil map
m2 := make(map[string]int) // 空 map

// nil map 读取返回零值
fmt.Println(m1["key"])  // 0

// nil map 写入会panic!
m1["key"] = 1  // ❌ panic: assignment to entry in nil map

// 空map可以正常操作
m2["key"] = 1  // ✅ 正常

所以永远用 make 或字面量创建map!

基本操作

m := make(map[string]int)

// 添加/修改
m["apple"] = 5
m["banana"] = 3

// 读取
fmt.Println(m["apple"])  // 5

// 读取不存在的key,返回零值
fmt.Println(m["grape"])  // 0

// 检查key是否存在
value, exists := m["apple"]
if exists {
    fmt.Println("apple =", value)
}

// 简写(常用!)
if value, ok := m["apple"]; ok {
    fmt.Println("apple =", value)
}

// 删除
delete(m, "apple")

// 获取长度
fmt.Println(len(m))  // 1

遍历Map

m := map[string]int{
    "apple":  5,
    "banana": 3,
    "orange": 8,
}

// 遍历key-value
for key, value := range m {
    fmt.Printf("%s: %d\n", key, value)
}

// 只遍历key
for key := range m {
    fmt.Println(key)
}

// 只遍历value
for _, value := range m {
    fmt.Println(value)
}

Map遍历是无序的!

每次遍历的顺序可能不同。如果需要有序,先取出key排序:

import "sort"

keys := make([]string, 0, len(m))
for k := range m {
    keys = append(keys, k)
}
sort.Strings(keys)

for _, k := range keys {
    fmt.Printf("%s: %d\n", k, m[k])
}

Map的值是引用类型

m1 := map[string]int{"a": 1, "b": 2}
m2 := m1  // 只复制了引用!

m2["a"] = 100
fmt.Println(m1["a"])  // 100(m1也变了!)

Map的键类型

Map的键必须是可比较的类型:

// ✅ 可以作为key
map[string]int     // 字符串
map[int]string     // 整数
map[bool]string    // 布尔
map[float64]int    // 浮点数(不推荐)

// ❌ 不能作为key
map[[]int]string   // 切片不行
map[map[string]int]string  // map不行

常见用法

统计字符出现次数

func countChars(s string) map[rune]int {
    counts := make(map[rune]int)
    for _, char := range s {
        counts[char]++
    }
    return counts
}

result := countChars("hello world")
for char, count := range result {
    fmt.Printf("%c: %d\n", char, count)
}

使用Map实现Set

Go没有内置Set,可以用Map模拟:

// 用map[T]bool实现Set
set := make(map[string]bool)

// 添加元素
set["apple"] = true
set["banana"] = true

// 检查元素是否存在
if set["apple"] {
    fmt.Println("apple在集合中")
}

// 删除元素
delete(set, "apple")

// 更省内存的方式:map[T]struct{}
set2 := make(map[string]struct{})
set2["apple"] = struct{}{}

if _, exists := set2["apple"]; exists {
    fmt.Println("apple在集合中")
}

分组统计

type Person struct {
    Name string
    City string
}

people := []Person{
    {"张三", "北京"},
    {"李四", "上海"},
    {"王五", "北京"},
    {"赵六", "上海"},
    {"钱七", "深圳"},
}

// 按城市分组
byCity := make(map[string][]Person)
for _, p := range people {
    byCity[p.City] = append(byCity[p.City], p)
}

for city, persons := range byCity {
    fmt.Printf("%s: %d人\n", city, len(persons))
    for _, p := range persons {
        fmt.Printf("  - %s\n", p.Name)
    }
}

缓存/备忘录模式

// 带缓存的斐波那契
var cache = make(map[int]int)

func fibonacci(n int) int {
    if n <= 1 {
        return n
    }
    
    // 检查缓存
    if v, ok := cache[n]; ok {
        return v
    }
    
    // 计算并缓存
    result := fibonacci(n-1) + fibonacci(n-2)
    cache[n] = result
    return result
}

嵌套Map

// 二维map
students := map[string]map[string]int{
    "张三": {
        "语文": 85,
        "数学": 92,
        "英语": 78,
    },
    "李四": {
        "语文": 90,
        "数学": 88,
        "英语": 95,
    },
}

// 访问
fmt.Println(students["张三"]["数学"])  // 92

// 添加新学生(需要先初始化内层map)
students["王五"] = make(map[string]int)
students["王五"]["语文"] = 80
// 或者
students["赵六"] = map[string]int{
    "语文": 75,
    "数学": 82,
}

并发安全

普通map不是并发安全的!并发读写会panic。

// ❌ 危险!并发读写
m := make(map[string]int)
go func() { m["a"] = 1 }()
go func() { _ = m["a"] }()

// ✅ 使用 sync.Map(Go 1.9+)
var sm sync.Map

sm.Store("key", "value")
value, ok := sm.Load("key")
sm.Delete("key")
sm.Range(func(key, value interface{}) bool {
    fmt.Printf("%v: %v\n", key, value)
    return true  // 继续遍历
})

// ✅ 或使用互斥锁
var mu sync.Mutex
m := make(map[string]int)

mu.Lock()
m["key"] = 1
mu.Unlock()

实战案例:单词频率统计

package main

import (
    "fmt"
    "sort"
    "strings"
)

func main() {
    text := `Go is an open source programming language 
    that makes it easy to build simple reliable and efficient software
    Go is expressive concise clean and efficient`
    
    // 统计词频
    words := strings.Fields(strings.ToLower(text))
    freq := make(map[string]int)
    for _, word := range words {
        freq[word]++
    }
    
    // 按频率排序
    type wordCount struct {
        word  string
        count int
    }
    
    var wcs []wordCount
    for w, c := range freq {
        wcs = append(wcs, wordCount{w, c})
    }
    
    sort.Slice(wcs, func(i, j int) bool {
        return wcs[i].count > wcs[j].count
    })
    
    // 打印Top 10
    fmt.Println("词频统计 Top 10:")
    for i, wc := range wcs {
        if i >= 10 {
            break
        }
        fmt.Printf("%2d. %-15s %d次\n", i+1, wc.word, wc.count)
    }
}

输出:

词频统计 Top 10:
 1. and             2次
 2. go              2次
 3. is              2次
 4. efficient       2次
 5. an              1次
 6. open            1次
 7. source          1次
 ...

实战案例:简易购物车

package main

import "fmt"

type Product struct {
    Name  string
    Price float64
}

type Cart struct {
    items map[string]int  // 商品名 -> 数量
    products map[string]Product
}

func NewCart() *Cart {
    return &Cart{
        items:    make(map[string]int),
        products: make(map[string]Product),
    }
}

func (c *Cart) Add(p Product, qty int) {
    c.products[p.Name] = p
    c.items[p.Name] += qty
}

func (c *Cart) Remove(name string) {
    delete(c.items, name)
}

func (c *Cart) Total() float64 {
    total := 0.0
    for name, qty := range c.items {
        total += c.products[name].Price * float64(qty)
    }
    return total
}

func (c *Cart) Show() {
    fmt.Println("购物车清单:")
    fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
    for name, qty := range c.items {
        p := c.products[name]
        subtotal := p.Price * float64(qty)
        fmt.Printf("%-10s ¥%.2f × %d = ¥%.2f\n", name, p.Price, qty, subtotal)
    }
    fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
    fmt.Printf("总计: ¥%.2f\n", c.Total())
}

func main() {
    cart := NewCart()
    
    cart.Add(Product{"苹果", 5.5}, 3)
    cart.Add(Product{"香蕉", 3.0}, 2)
    cart.Add(Product{"橙子", 4.5}, 5)
    
    cart.Show()
}

输出:

购物车清单:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
苹果         ¥5.50 × 3 = ¥16.50
香蕉         ¥3.00 × 2 = ¥6.00
橙子         ¥4.50 × 5 = ¥22.50
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
总计: ¥45.00

练习

  1. 创建一个map存储5个学生的成绩,计算平均分
  2. 实现一个函数,统计字符串中每个字符出现的次数
  3. 使用map实现两个切片的交集
参考答案
package main

import "fmt"

// 1. 学生成绩
func avgScore() {
    scores := map[string]float64{
        "张三": 85,
        "李四": 92,
        "王五": 78,
        "赵六": 88,
        "钱七": 95,
    }
    
    total := 0.0
    for _, score := range scores {
        total += score
    }
    fmt.Printf("平均分: %.2f\n", total/float64(len(scores)))
}

// 2. 字符统计
func countChars(s string) map[rune]int {
    result := make(map[rune]int)
    for _, c := range s {
        result[c]++
    }
    return result
}

// 3. 切片交集
func intersection(a, b []int) []int {
    set := make(map[int]bool)
    for _, v := range a {
        set[v] = true
    }
    
    result := []int{}
    for _, v := range b {
        if set[v] {
            result = append(result, v)
            delete(set, v) // 避免重复
        }
    }
    return result
}

func main() {
    // 1
    avgScore()
    
    // 2
    counts := countChars("hello world")
    for c, n := range counts {
        fmt.Printf("%c: %d\n", c, n)
    }
    
    // 3
    a := []int{1, 2, 3, 4, 5}
    b := []int{3, 4, 5, 6, 7}
    fmt.Println("交集:", intersection(a, b))  // [3 4 5]
}

Map掌握了,下一节学习结构体——Go的"类"!

最近更新: 2025/12/27 13:26
Contributors: 王长安
Prev
数组与切片
Next
结构体