第一次提交
This commit is contained in:
266
src/programming/backend/go/Go基础语法/03slice和map.md
Normal file
266
src/programming/backend/go/Go基础语法/03slice和map.md
Normal file
@@ -0,0 +1,266 @@
|
||||
---
|
||||
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 的并发写风险与安全方案**
|
||||
|
||||
我们将通过一个可运行的示例程序,在你的环境中一步步验证这些行为。
|
||||
|
||||
<!-- more -->
|
||||
|
||||
---
|
||||
|
||||
## ✅ 步骤 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 编程愉快 🚀
|
||||
Reference in New Issue
Block a user