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
练习
- 创建一个map存储5个学生的成绩,计算平均分
- 实现一个函数,统计字符串中每个字符出现的次数
- 使用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的"类"!
