--- icon: mdi:table-arrow-down date: 2025-05-22 title: Go 基础语法 - Slice 和 Map category: - Go - 后端 - 编程 - 教程 - 数据结构 - slice - map - 扩容机制 - 深拷贝 - 浅拷贝 - 并发安全 - sync.Map --- 当然可以!以下是基于你当前环境(Linux Mint XFCE、Go 1.22.2、项目目录 `/home/liumangmang/GolandProjects`)整理的 **Go 语言 slice 与 map 深入实践步骤**,涵盖: - **slice 的扩容机制** - **slice 的深拷贝 vs 浅拷贝** - **map 的基本使用** - **map 的并发写风险与安全方案** 我们将通过一个可运行的示例程序,在你的环境中一步步验证这些行为。 --- ## ✅ 步骤 1:创建新项目 ```bash cd /home/liumangmang/GolandProjects mkdir go-slice-map && cd go-slice-map go mod init go-slice-map ``` --- ## ✅ 步骤 2:编写演示代码(main.go) 创建 `main.go`: ```bash nano main.go ``` 粘贴以下完整代码(含详细注释): ```go package main import ( "fmt" "runtime" "sync" "time" ) func main() { fmt.Println("=== 1. Slice 扩容机制 ===") demoSliceGrowth() fmt.Println("\n=== 2. Slice 深拷贝 vs 浅拷贝 ===") demoSliceCopy() fmt.Println("\n=== 3. Map 基本使用 ===") demoMapUsage() fmt.Println("\n=== 4. Map 并发写风险(会 panic!)===") // ⚠️ 下面这行默认注释掉,避免程序崩溃 // demoUnsafeConcurrentMap() fmt.Println("\n=== 5. 安全的并发 map(sync.Map)===") demoSafeConcurrentMap() } // 1. 演示 slice 扩容机制 func demoSliceGrowth() { var s []int fmt.Printf("初始: len=%d, cap=%d, addr=%p\n", len(s), cap(s), s) for i := 0; i < 10; i++ { s = append(s, i) fmt.Printf("append %d: len=%d, cap=%d, addr=%p\n", i, len(s), cap(s), s) } // 观察:当容量不足时,底层数组会重新分配,地址改变 } // 2. 深拷贝 vs 浅拷贝 func demoSliceCopy() { original := []int{1, 2, 3, 4, 5} shallow := original // 浅拷贝:共享底层数组 deep := make([]int, len(original)) copy(deep, original) // 深拷贝:独立底层数组 fmt.Println("修改前:") fmt.Println("original:", original) fmt.Println("shallow :", shallow) fmt.Println("deep :", deep) // 修改原 slice original[0] = 999 fmt.Println("\n修改 original[0] = 999 后:") fmt.Println("original:", original) // [999, 2, 3, 4, 5] fmt.Println("shallow :", shallow) // [999, 2, 3, 4, 5] ← 被影响! fmt.Println("deep :", deep) // [1, 2, 3, 4, 5] ← 不受影响 } // 3. Map 基本使用 func demoMapUsage() { m := make(map[string]int) m["apple"] = 5 m["banana"] = 3 fmt.Println("map 内容:", m) fmt.Println("apple 数量:", m["apple"]) // 检查 key 是否存在 if count, ok := m["orange"]; ok { fmt.Println("orange 存在,数量:", count) } else { fmt.Println("orange 不存在") } // 删除 key delete(m, "apple") fmt.Println("删除 apple 后:", m) } // 4. 不安全的并发 map 写入(会导致 panic) func demoUnsafeConcurrentMap() { m := make(map[int]int) var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func(key int) { defer wg.Done() m[key] = key * 10 // 并发写 → fatal error: concurrent map writes }(i) } wg.Wait() fmt.Println("unsafe map done (should not reach here)") } // 5. 使用 sync.Map 实现安全并发 func demoSafeConcurrentMap() { var sm sync.Map var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func(key int) { defer wg.Done() sm.Store(key, key*10) }(i) } wg.Wait() // 读取部分值验证 sm.Range(func(key, value any) bool { if key.(int) < 5 { // 只打印前几个 fmt.Printf("key=%v, value=%v\n", key, value) } return true // 继续遍历 }) } ``` 保存退出(`Ctrl+O` → Enter → `Ctrl+X`)。 --- ## ✅ 步骤 3:格式化并运行 ```bash go fmt go run . ``` ### 预期输出(节选): ``` === 1. Slice 扩容机制 === 初始: len=0, cap=0, addr=0x0 append 0: len=1, cap=1, addr=0xc0000180c0 append 1: len=2, cap=2, addr=0xc0000180e0 append 2: len=3, cap=4, addr=0xc00001c0a0 ← 容量翻倍,地址变了! ... === 2. Slice 深拷贝 vs 浅拷贝 === 修改前: original: [1 2 3 4 5] shallow : [1 2 3 4 5] deep : [1 2 3 4 5] 修改 original[0] = 999 后: original: [999 2 3 4 5] shallow : [999 2 3 4 5] deep : [1 2 3 4 5] === 3. Map 基本使用 === map 内容: map[apple:5 banana:3] apple 数量: 5 orange 不存在 删除 apple 后: map[banana:3] === 4. Map 并发写风险(会 panic!)=== === 5. 安全的并发 map(sync.Map)=== key=0, value=0 key=1, value=10 key=2, value=20 key=3, value=30 key=4, value=40 ``` > 🔒 注意:`demoUnsafeConcurrentMap()` 默认被注释。如果你想**亲眼看到 panic**,可以取消注释它再运行——程序会崩溃并输出: > > ``` > fatal error: concurrent map writes > ``` --- ## 📚 关键知识点总结 ### 🔸 Slice 扩容机制(Go 1.22) - 当 `cap < 1024`:扩容为 **2 倍** - 当 `cap >= 1024`:扩容为 **1.25 倍**(实际策略可能微调,但趋势是渐进增长) - 扩容时会**分配新底层数组**,旧 slice 和新 slice **不再共享数据** ### 🔸 深拷贝 vs 浅拷贝 | 方式 | 是否共享底层数组 | 是否安全独立修改 | |------|------------------|------------------| | `s2 := s1` | ✅ 是 | ❌ 否 | | `copy(s2, s1)` | ❌ 否 | ✅ 是 | > 💡 提醒:`copy` 要求目标 slice 已分配内存(用 `make`)! ### 🔸 Map 并发安全 - **普通 map 不是并发安全的**:多个 goroutine 同时写会 panic - **解决方案**: - 使用 `sync.Mutex` 或 `sync.RWMutex` 保护 map - 或直接使用 `sync.Map`(适用于读多写少场景) > ⚠️ `sync.Map` 性能不一定比加锁的 map 高,仅在特定场景推荐使用。 --- ## ✅ 在 GoLand 中调试建议 1. 打开项目 `/home/liumangmang/GolandProjects/go-slice-map` 2. 在 `demoSliceGrowth()` 中设置断点,观察每次 `append` 后地址变化 3. 尝试取消注释 `demoUnsafeConcurrentMap()`,运行看 panic(有助于理解并发风险) --- 如果你希望继续学习 **channel、goroutine、context、错误处理** 或 **Go 与 Java 对比(如 slice vs ArrayList)**,欢迎随时告诉我!祝你 Go 编程愉快 🚀