目录
- 切片在后端中扮演什么角色
- [底层结构:len、cap 与底层数组](#底层结构:len、cap 与底层数组)
- [创建、初始化与 nil 切片](#创建、初始化与 nil 切片)
- 切片表达式:截取与三索引
- [append 与扩容机制](#append 与扩容机制)
- copy、克隆与元素删除
- 函数传参、共享与内存滞留
- [后端相关类型:JSON 与 \[\]byte](#后端相关类型:JSON 与 []byte)
- 并发环境下的切片
- 知识串联与综合练习
- 附录:速查与延伸阅读
1. 切片在后端中扮演什么角色
1.1 核心概念
切片(slice)是对底层数组的视图,由三部分描述:
go
// 概念模型,帮助理解内存布局(非标准库导出类型)
type slice struct {
ptr *T // 指向底层数组中某个元素
len int // 当前「逻辑长度」
cap int // 从 ptr 起到底层数组末尾的容量
}
后端代码里,切片是最常用的「变长集合」:
- 查询结果:
[]User、[]Order - 请求参数:
ids []int64、tags []string - 缓冲数据:
[]byte(读 body、加解密、日志) - 中间结果:过滤、合并、分页后的列表
1.2 与数组的对比(先建立区分)
数组 [N]T |
切片 []T |
|
|---|---|---|
| 长度 | 编译期固定,是类型的一部分 | 运行时可变 |
| 赋值/传参 | 复制整个数组 | 复制 slice header,共享底层数组 |
| 比较 | 可用 == |
不能比较内容(用 slices.Equal) |
| 后端中的位置 | 少见,如 [32]byte 小缓冲 |
几乎无处不在 |
go
var buf [4]byte // 数组,len 固定为 4
var users []User // 切片,长度随 append 变化
1.3 示例:一条请求中的切片
下面用简化代码说明切片在后端链路中的位置(重在理解,不是项目模板):
go
// 1. 反序列化:JSON 数组 → []int64
var req struct {
IDs []int64 `json:"ids"`
}
// 2. 查询:数据库多行 → append 进 []Order
orders := make([]Order, 0, len(req.IDs))
// ... 循环 rows.Scan,orders = append(orders, o)
// 3. 响应:[]Order → JSON 数组
// json.Marshal(orders)
1.4 时序图:一条请求中切片的流转
下图展示切片在后端链路中何时被创建、传递、填充(各层之间传递的是 slice header,底层数组在 DAO 层逐步填充):
encoding/json DAO / DB Service Handler 客户端 encoding/json DAO / DB Service Handler 客户端 #mermaid-svg-rHSTDqO6jZLKknSj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rHSTDqO6jZLKknSj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rHSTDqO6jZLKknSj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rHSTDqO6jZLKknSj .error-icon{fill:#552222;}#mermaid-svg-rHSTDqO6jZLKknSj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rHSTDqO6jZLKknSj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rHSTDqO6jZLKknSj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rHSTDqO6jZLKknSj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rHSTDqO6jZLKknSj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rHSTDqO6jZLKknSj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rHSTDqO6jZLKknSj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rHSTDqO6jZLKknSj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rHSTDqO6jZLKknSj .marker.cross{stroke:#333333;}#mermaid-svg-rHSTDqO6jZLKknSj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rHSTDqO6jZLKknSj p{margin:0;}#mermaid-svg-rHSTDqO6jZLKknSj .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-rHSTDqO6jZLKknSj text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-rHSTDqO6jZLKknSj .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-rHSTDqO6jZLKknSj .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-rHSTDqO6jZLKknSj .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-rHSTDqO6jZLKknSj .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-rHSTDqO6jZLKknSj #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-rHSTDqO6jZLKknSj .sequenceNumber{fill:white;}#mermaid-svg-rHSTDqO6jZLKknSj #sequencenumber{fill:#333;}#mermaid-svg-rHSTDqO6jZLKknSj #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-rHSTDqO6jZLKknSj .messageText{fill:#333;stroke:none;}#mermaid-svg-rHSTDqO6jZLKknSj .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-rHSTDqO6jZLKknSj .labelText,#mermaid-svg-rHSTDqO6jZLKknSj .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-rHSTDqO6jZLKknSj .loopText,#mermaid-svg-rHSTDqO6jZLKknSj .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-rHSTDqO6jZLKknSj .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-rHSTDqO6jZLKknSj .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-rHSTDqO6jZLKknSj .noteText,#mermaid-svg-rHSTDqO6jZLKknSj .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-rHSTDqO6jZLKknSj .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-rHSTDqO6jZLKknSj .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-rHSTDqO6jZLKknSj .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-rHSTDqO6jZLKknSj .actorPopupMenu{position:absolute;}#mermaid-svg-rHSTDqO6jZLKknSj .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-rHSTDqO6jZLKknSj .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-rHSTDqO6jZLKknSj .actor-man circle,#mermaid-svg-rHSTDqO6jZLKknSj line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-rHSTDqO6jZLKknSj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 分配切片,填充 ID loop rows.Next POST {"ids":1,2,3} Unmarshal → req.IDs \[\]int64 传入 req.IDs(拷贝 header) 查询 orders(传入 ids) make(\[\]Order, 0, cap) append(orders, row) 返回 \[\]Order 返回 \[\]OrderDTO Marshal(dtoList) {"orders":...} HTTP 200 + JSON 数组
本章要点:切片不是「存放数据的容器」,而是「指向某段连续内存的窗口」。后面所有行为都由此推导。
1.5 理解检验
- 切片本身存储元素吗?(否,存的是 ptr + len + cap)
[]User和[10]User能互相赋值吗?(不能,类型不同)
2. 底层结构:len、cap 与底层数组
2.1 len 与 cap 的含义
- len :当前能访问的元素个数,合法下标
0~len-1 - cap :从当前
ptr位置到底层数组末尾,不重新分配的情况下最多能扩展到多少元素
go
s := make([]int, 3, 8)
// len=3:可读写 s[0]、s[1]、s[2]
// cap=8:还可 append 5 个元素而不换底层数组(在多数情况下)
2.2 图示
底层数组: [ 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 ]
索引: 0 1 2 3 4 5 6 7
s := arr[2:5] // 模拟「只取查询结果第 3~5 条」
ptr ──► 索引 2
┌──────────────────────┐
s: │ 30 | 40 | 50 │ ... │
└──────────────────────┘
len = 3 cap = 6(从索引 2 到数组末尾)
2.3 时序图:截取操作如何改变 header
arr[2:5] 不拷贝元素,只新建一个指向同一底层数组的 slice header:
底层数组 s = arr2:5 arr (header) 代码 底层数组 s = arr2:5 arr (header) 代码 #mermaid-svg-oIqEZ3TBj6CKl6WS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oIqEZ3TBj6CKl6WS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oIqEZ3TBj6CKl6WS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oIqEZ3TBj6CKl6WS .error-icon{fill:#552222;}#mermaid-svg-oIqEZ3TBj6CKl6WS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oIqEZ3TBj6CKl6WS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oIqEZ3TBj6CKl6WS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oIqEZ3TBj6CKl6WS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oIqEZ3TBj6CKl6WS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oIqEZ3TBj6CKl6WS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oIqEZ3TBj6CKl6WS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oIqEZ3TBj6CKl6WS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oIqEZ3TBj6CKl6WS .marker.cross{stroke:#333333;}#mermaid-svg-oIqEZ3TBj6CKl6WS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oIqEZ3TBj6CKl6WS p{margin:0;}#mermaid-svg-oIqEZ3TBj6CKl6WS .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-oIqEZ3TBj6CKl6WS text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-oIqEZ3TBj6CKl6WS .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-oIqEZ3TBj6CKl6WS .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-oIqEZ3TBj6CKl6WS .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-oIqEZ3TBj6CKl6WS .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-oIqEZ3TBj6CKl6WS #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-oIqEZ3TBj6CKl6WS .sequenceNumber{fill:white;}#mermaid-svg-oIqEZ3TBj6CKl6WS #sequencenumber{fill:#333;}#mermaid-svg-oIqEZ3TBj6CKl6WS #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-oIqEZ3TBj6CKl6WS .messageText{fill:#333;stroke:none;}#mermaid-svg-oIqEZ3TBj6CKl6WS .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-oIqEZ3TBj6CKl6WS .labelText,#mermaid-svg-oIqEZ3TBj6CKl6WS .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-oIqEZ3TBj6CKl6WS .loopText,#mermaid-svg-oIqEZ3TBj6CKl6WS .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-oIqEZ3TBj6CKl6WS .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-oIqEZ3TBj6CKl6WS .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-oIqEZ3TBj6CKl6WS .noteText,#mermaid-svg-oIqEZ3TBj6CKl6WS .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-oIqEZ3TBj6CKl6WS .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-oIqEZ3TBj6CKl6WS .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-oIqEZ3TBj6CKl6WS .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-oIqEZ3TBj6CKl6WS .actorPopupMenu{position:absolute;}#mermaid-svg-oIqEZ3TBj6CKl6WS .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-oIqEZ3TBj6CKl6WS .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-oIqEZ3TBj6CKl6WS .actor-man circle,#mermaid-svg-oIqEZ3TBj6CKl6WS line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-oIqEZ3TBj6CKl6WS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 新建 header 副本 ptr→索引2, len=3, cap=6 arr 已存在,ptr→索引0, len=8, cap=8 引用整块数组 执行 arr2:5 引用同一底层数组(偏移 +2) 读 s0 实际访问 arr2
2.4 后端类比(帮助记忆,不是规范)
| 概念 | 可类比为 |
|---|---|
| 底层数组 | 一次查库拿到的完整结果集(或缓冲区) |
| len | 当前对外暴露的条数 |
| cap | 这块缓冲区还能「原地」追加多少条 |
类比有限:真实堆内存由 Go 运行时管理,但 len/cap 分工 这一层理解是准确的。
2.5 理解检验
make([]int, 5, 10)的 len 和 cap 各是多少?(5 和 10)- 能访问
s[5]吗?(不能,s[5]需要 len > 5)
3. 创建、初始化与 nil 切片
3.1 四种常见创建方式
go
// ① 零值:nil 切片
var s1 []int
// ② 字面量
s2 := []int{1, 2, 3}
// ③ make
s3 := make([]int, 5) // len=5, cap=5,元素为 0
s4 := make([]int, 0, 10) // len=0, cap=10,常用于「已知上限的列表」
// ④ 从数组或其它切片截取
arr := [5]int{1, 2, 3, 4, 5}
s5 := arr[1:4] // [2, 3, 4]
3.2 make 的 len 与 cap
go
s := make([]T, len, cap) // 约束:0 <= len <= cap
| 写法 | len | cap | 说明 |
|---|---|---|---|
make([]T, 0) |
0 | 0 | 空切片,可 append |
make([]T, 0, n) |
0 | n | 预分配容量,后端列表常用 |
make([]T, n) |
n | n | n 个零值元素,可直接 s[i]= |
学习点 :make([]T, 0, pageSize) 不是「已经有 pageSize 个元素」,而是「准备好能无扩容地 append 约 pageSize 个」。
3.3 nil 切片 vs 空切片
go
var a []int // nil:== nil 为 true
b := []int{} // 空切片:== nil 为 false
c := make([]int, 0) // 空切片
fmt.Println(len(a), len(b), len(c)) // 都是 0
fmt.Println(a == nil) // true
fmt.Println(b == nil) // false
| nil 切片 | 空切片(非 nil) | |
|---|---|---|
== nil |
true | false |
len / cap |
0 / 0 | 0 / 0 或 0 / n |
append |
正常可用 | 正常可用 |
| JSON 序列化 | null |
[] |
学习点 :两者「用起来」很像,但在 JSON 语义 和 是否为 nil 上不同。第 8 章会结合 encoding/json 展开。
3.4 理解检验
var ids []int64后直接append(ids, 1)合法吗?(合法)make([]User, 20)和make([]User, 0, 20)的区别?(前者有 20 个零值 User;后者 len 为 0,仅预留 cap)
4. 切片表达式:截取与三索引
4.1 语法
go
s[low:high] // 下标 [low, high),cap = 原 cap - low
s[low:high:max] // 三索引,cap = max - low
s[:high] // low 默认为 0
s[low:] // high 默认为 len
s[:] // 完整切片
边界:0 <= low <= high <= len;三索引时还有 high <= max <= cap。
4.2 两索引与三索引的本质区别
go
data := []int{0, 1, 2, 3, 4, 5}
a := data[1:3] // [1, 2], len=2, cap=5
b := data[1:3:3] // [1, 2], len=2, cap=2
| 表达式 | len | cap | 对 data 做 append 时 |
|---|---|---|---|
data[1:3] |
2 | 5 | 可能覆盖 data[3] 及之后 |
data[1:3:3] |
2 | 2 | 不会覆盖,append 会触发新数组 |
4.3 时序图:子切片 append 污染父切片
经典场景 s2 := s[:2]; s2 = append(s2, 99) --- 两索引子切片 cap 未受限,append 原地写入:
底层数组 s2 = s:2 s 1,2,3 main 底层数组 s2 = s:2 s 1,2,3 main #mermaid-svg-6rhU6dnrqNS4wxBb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6rhU6dnrqNS4wxBb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6rhU6dnrqNS4wxBb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6rhU6dnrqNS4wxBb .error-icon{fill:#552222;}#mermaid-svg-6rhU6dnrqNS4wxBb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6rhU6dnrqNS4wxBb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6rhU6dnrqNS4wxBb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6rhU6dnrqNS4wxBb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6rhU6dnrqNS4wxBb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6rhU6dnrqNS4wxBb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6rhU6dnrqNS4wxBb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6rhU6dnrqNS4wxBb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6rhU6dnrqNS4wxBb .marker.cross{stroke:#333333;}#mermaid-svg-6rhU6dnrqNS4wxBb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6rhU6dnrqNS4wxBb p{margin:0;}#mermaid-svg-6rhU6dnrqNS4wxBb .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6rhU6dnrqNS4wxBb text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-6rhU6dnrqNS4wxBb .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6rhU6dnrqNS4wxBb .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-6rhU6dnrqNS4wxBb .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-6rhU6dnrqNS4wxBb .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-6rhU6dnrqNS4wxBb #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-6rhU6dnrqNS4wxBb .sequenceNumber{fill:white;}#mermaid-svg-6rhU6dnrqNS4wxBb #sequencenumber{fill:#333;}#mermaid-svg-6rhU6dnrqNS4wxBb #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-6rhU6dnrqNS4wxBb .messageText{fill:#333;stroke:none;}#mermaid-svg-6rhU6dnrqNS4wxBb .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6rhU6dnrqNS4wxBb .labelText,#mermaid-svg-6rhU6dnrqNS4wxBb .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-6rhU6dnrqNS4wxBb .loopText,#mermaid-svg-6rhU6dnrqNS4wxBb .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-6rhU6dnrqNS4wxBb .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6rhU6dnrqNS4wxBb .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-6rhU6dnrqNS4wxBb .noteText,#mermaid-svg-6rhU6dnrqNS4wxBb .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-6rhU6dnrqNS4wxBb .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6rhU6dnrqNS4wxBb .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6rhU6dnrqNS4wxBb .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6rhU6dnrqNS4wxBb .actorPopupMenu{position:absolute;}#mermaid-svg-6rhU6dnrqNS4wxBb .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-6rhU6dnrqNS4wxBb .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6rhU6dnrqNS4wxBb .actor-man circle,#mermaid-svg-6rhU6dnrqNS4wxBb line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-6rhU6dnrqNS4wxBb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ptr 同 s,len=2, cap=3 len+n ≤ cap,原地扩展 s2 被覆盖,父切片「被动修改」 s 创建,ptr→arr0, len=3, cap=3 1 \| 2 \| 3 s:2 截取 共享同一数组 append(s2, 99) 写入索引2 → 99 读 s 读到 1, 2, 99
对比 :三索引 s[1:3:3] 或 slices.Clone 后 append,会走扩容分支,写入新数组,不污染父切片(见第 5 章时序图)。
4.4 学习示例:分页截取
go
// 假设 users 有 100 条,取第 20~29 条(page=3, size=10)
page := users[20:30] // len=10, cap=80
// 若把 page 传给会 append 的函数,且未扩容,可能改动 users[30] 一带
// 学习时的安全写法:
page := users[20:30:30] // 限制 cap
// 或
page := slices.Clone(users[20:30]) // Go 1.21+
4.5 经典思考题
go
s := []int{1, 2, 3}
s2 := s[:2]
s2 = append(s2, 99)
fmt.Println(s) // ?
答案与解释
输出 [1 2 99]。s[:2] 与 s 共享底层数组;append 在 cap 允许时原地写入,覆盖了 s[2]。
4.6 理解检验
s[1:4:4]的 cap 是多少?(3)- 什么时候应优先考虑三索引或 Clone?(子切片会交给可能
append的代码,且不能与父切片共享尾部空间时)
5. append 与扩容机制
5.1 基本规则
go
s = append(s, x) // 必须接收返回值
s = append(s, a, b, c)
s = append(s, other...) // 展开另一个切片
append 可能:
- 原地扩展 len (
len+n <= cap)→ 仍共享原底层数组 - 分配新数组、拷贝、返回新 header (
len+n > cap)→ 与原切片脱钩
5.2 时序图:append 的两条路径
路径 A --- 容量足够(原地扩展)
底层数组 slice header append 调用方 底层数组 slice header append 调用方 #mermaid-svg-YdR9tI9SJEJtbJIj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YdR9tI9SJEJtbJIj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YdR9tI9SJEJtbJIj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YdR9tI9SJEJtbJIj .error-icon{fill:#552222;}#mermaid-svg-YdR9tI9SJEJtbJIj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YdR9tI9SJEJtbJIj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YdR9tI9SJEJtbJIj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YdR9tI9SJEJtbJIj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YdR9tI9SJEJtbJIj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YdR9tI9SJEJtbJIj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YdR9tI9SJEJtbJIj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YdR9tI9SJEJtbJIj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YdR9tI9SJEJtbJIj .marker.cross{stroke:#333333;}#mermaid-svg-YdR9tI9SJEJtbJIj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YdR9tI9SJEJtbJIj p{margin:0;}#mermaid-svg-YdR9tI9SJEJtbJIj .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YdR9tI9SJEJtbJIj text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-YdR9tI9SJEJtbJIj .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-YdR9tI9SJEJtbJIj .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-YdR9tI9SJEJtbJIj .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-YdR9tI9SJEJtbJIj .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-YdR9tI9SJEJtbJIj #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-YdR9tI9SJEJtbJIj .sequenceNumber{fill:white;}#mermaid-svg-YdR9tI9SJEJtbJIj #sequencenumber{fill:#333;}#mermaid-svg-YdR9tI9SJEJtbJIj #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-YdR9tI9SJEJtbJIj .messageText{fill:#333;stroke:none;}#mermaid-svg-YdR9tI9SJEJtbJIj .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YdR9tI9SJEJtbJIj .labelText,#mermaid-svg-YdR9tI9SJEJtbJIj .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-YdR9tI9SJEJtbJIj .loopText,#mermaid-svg-YdR9tI9SJEJtbJIj .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-YdR9tI9SJEJtbJIj .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-YdR9tI9SJEJtbJIj .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-YdR9tI9SJEJtbJIj .noteText,#mermaid-svg-YdR9tI9SJEJtbJIj .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-YdR9tI9SJEJtbJIj .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YdR9tI9SJEJtbJIj .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YdR9tI9SJEJtbJIj .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YdR9tI9SJEJtbJIj .actorPopupMenu{position:absolute;}#mermaid-svg-YdR9tI9SJEJtbJIj .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-YdR9tI9SJEJtbJIj .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YdR9tI9SJEJtbJIj .actor-man circle,#mermaid-svg-YdR9tI9SJEJtbJIj line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-YdR9tI9SJEJtbJIj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 所有共享此数组的切片都可能看到新元素 append(s, x),len+1 ≤ cap 在 ptr+len 处写入 x len ← len+1(ptr 不变) 返回同一底层数组的 header
路径 B --- 容量不足(扩容)
新底层数组 旧底层数组 runtime.growslice append 调用方 新底层数组 旧底层数组 runtime.growslice append 调用方 #mermaid-svg-iuUpjGS0vDkW8y6N{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iuUpjGS0vDkW8y6N .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iuUpjGS0vDkW8y6N .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iuUpjGS0vDkW8y6N .error-icon{fill:#552222;}#mermaid-svg-iuUpjGS0vDkW8y6N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iuUpjGS0vDkW8y6N .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iuUpjGS0vDkW8y6N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iuUpjGS0vDkW8y6N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iuUpjGS0vDkW8y6N .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iuUpjGS0vDkW8y6N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iuUpjGS0vDkW8y6N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iuUpjGS0vDkW8y6N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iuUpjGS0vDkW8y6N .marker.cross{stroke:#333333;}#mermaid-svg-iuUpjGS0vDkW8y6N svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iuUpjGS0vDkW8y6N p{margin:0;}#mermaid-svg-iuUpjGS0vDkW8y6N .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-iuUpjGS0vDkW8y6N text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-iuUpjGS0vDkW8y6N .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-iuUpjGS0vDkW8y6N .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-iuUpjGS0vDkW8y6N .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-iuUpjGS0vDkW8y6N .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-iuUpjGS0vDkW8y6N #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-iuUpjGS0vDkW8y6N .sequenceNumber{fill:white;}#mermaid-svg-iuUpjGS0vDkW8y6N #sequencenumber{fill:#333;}#mermaid-svg-iuUpjGS0vDkW8y6N #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-iuUpjGS0vDkW8y6N .messageText{fill:#333;stroke:none;}#mermaid-svg-iuUpjGS0vDkW8y6N .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-iuUpjGS0vDkW8y6N .labelText,#mermaid-svg-iuUpjGS0vDkW8y6N .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-iuUpjGS0vDkW8y6N .loopText,#mermaid-svg-iuUpjGS0vDkW8y6N .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-iuUpjGS0vDkW8y6N .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-iuUpjGS0vDkW8y6N .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-iuUpjGS0vDkW8y6N .noteText,#mermaid-svg-iuUpjGS0vDkW8y6N .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-iuUpjGS0vDkW8y6N .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-iuUpjGS0vDkW8y6N .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-iuUpjGS0vDkW8y6N .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-iuUpjGS0vDkW8y6N .actorPopupMenu{position:absolute;}#mermaid-svg-iuUpjGS0vDkW8y6N .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-iuUpjGS0vDkW8y6N .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-iuUpjGS0vDkW8y6N .actor-man circle,#mermaid-svg-iuUpjGS0vDkW8y6N line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-iuUpjGS0vDkW8y6N :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 无引用后可被 GC 回收 必须用 s = append(s,x) 接住新 header append(s, x),len+1 > cap 申请更大数组 分配新 cap(约 2x 或 1.25x) 拷贝旧元素 → New 写入新元素 x 返回新 ptr 的 header
5.3 扩容策略(了解即可)
| 情况 | 典型行为(Go 1.18+ 简化) |
|---|---|
| 较小 cap | 约按 2 倍增长 |
| 较大 cap | 约 1.25 倍 + 内存对齐 |
细节以 runtime.growslice 为准;学习阶段记住:小切片翻倍、大切片温和增长、应主动预分配 cap。
5.4 观察扩容(建议动手运行)
go
func observeGrowth() {
s := make([]int, 0)
prev := cap(s)
for i := 0; i < 20; i++ {
s = append(s, i)
if cap(s) != prev {
fmt.Printf("len=%d cap: %d → %d\n", len(s), prev, cap(s))
prev = cap(s)
}
}
}
5.5 与后端学习的联系
go
// 未预分配:多次扩容,分配与拷贝次数多
var list []Order
for rows.Next() {
// ...
list = append(list, o)
}
// 学过 cap 后应能读懂「为什么」这样更好:
list := make([]Order, 0, pageSize)
for rows.Next() {
list = append(list, o)
}
5.6 理解检验
append(s, 1)而不赋值给s,有什么问题?(可能丢元素;且 s 仍指向旧 header)len=2, cap=2时再append一个元素,底层数组一定变吗?(是,cap 不够则新分配)
6. copy、克隆与元素删除
6.1 copy
go
dst := make([]int, 3)
src := []int{1, 2, 3, 4, 5}
n := copy(dst, src) // n=3,dst=[1,2,3]
- 拷贝个数 =
min(len(dst), len(src)) - 支持重叠区域(类似
memmove) - 独立底层数组 时,改
dst不影响src
6.2 克隆切片
go
clone := append([]int(nil), s...)
// Go 1.21+
clone := slices.Clone(s)
何时需要克隆(学习判断):
- 要把子集交给会修改或 append 的逻辑,且不能影响原数据
- 从大缓冲中取一小段长期保存(避免内存滞留,见第 7 章)
6.3 删除元素
go
// 保序删除下标 i
s = append(s[:i], s[i+1:]...)
// 不保序,O(1)
s[i] = s[len(s)-1]
s = s[:len(s)-1]
// Go 1.21+
s = slices.Delete(s, i, i+1)
s = slices.DeleteFunc(s, func(v T) bool { return 条件(v) })
6.4 清空但保留容量
go
s = s[:0] // len=0,cap 不变,便于复用缓冲
6.5 理解检验
copy和append([]T(nil), s...)都能复制,区别是什么场景?(copy需已知len的 dst;append/Clone更常用于得一个全新切片)s = s[:0]后cap变吗?(不变)
7. 函数传参、共享与内存滞留
本章用生活类比 + 可运行小例子讲清楚:把切片传给函数后,到底谁会变、谁不会变。
7.0 到底是值拷贝,还是引用拷贝?
先给一句人话结论
传函数时,拷贝的是「切片的说明书」,不是「盒子里的全部数据」。
两份说明书(main 里一份、函数里一份)起初指着同一个盒子 ;改盒子里的东西,两边都看得到;但你在函数里换了一本新说明书、或改了说明书上的字数,main 手里那本不会跟着变。
别纠结「值拷贝还是引用拷贝」这两个词 --- Go 的切片两个都不是(和 Java 的引用、C 的指针不是一回事)。记住上面这句话就够用了。
类比:门牌号 + 仓库
想象切片是这样三样东西:
| 切片里的概念 | 类比成 |
|---|---|
| 底层数组(真正存数据的地方) | 一间仓库,里面按格子放货物 |
len(当前有几个元素) |
说明书写的:「现在对外展示 3 格货物」 |
cap(还能再塞多少) |
说明书写的:「这间仓库从当前位置起还能再放 5 格」 |
指针 ptr |
说明书上的门牌:「从仓库第几号格子开始算」 |
调用 modify(s) 时,Go 不会把整个仓库复制一份(那太慢了),而是复印了一张说明书交给函数:
main 手里: 说明书 A ──┐
函数手里: 说明书 B ──┴──► 同一间仓库
- 改仓库里的货 (
s[0] = 100)→ A 和 B 指的是同一间仓库,main 当然看得见。 - 在函数里把说明书 B 改成「目前 0 格货」 (
s = s[:0])→ 只改了 B,A 上还写着 3 格,main 不受影响。 - 在函数里 append 太多,仓库装不下,搬了新仓库 → 只有 B 指向新仓库,A 还指着旧的。
示例 1:改第一个元素 --- 两边都变
把下面代码完整复制 运行(建议用 Go Playground):
go
package main
import "fmt"
func changeFirst(s []int) {
s[0] = 999
fmt.Println("函数里看到:", s)
}
func main() {
nums := []int{10, 20, 30}
fmt.Println("调用前:", nums)
changeFirst(nums)
fmt.Println("调用后:", nums) // main 里也变了吗?
}
运行结果:
调用前: [10 20 30]
函数里看到: [999 20 30]
调用后: [999 20 30]
读懂它: 函数改的是仓库里的第一格,不是改「说明书」本身,所以 main 里的 nums 也跟着变。
很多人因此说「切片是引用传递」--- 感觉像 ,但准确说是:说明书复印了一份,仓库还是同一间。
示例 2:函数里 append --- 有时 main 变,有时不变
go
package main
import "fmt"
// 情况 A:仓库还有空位(cap 够)
func appendInRoom(s []int) {
s = append(s, 99)
fmt.Println("函数里:", s, "len=", len(s), "cap=", cap(s))
}
func main() {
// cap=5,len=3,后面还有空位
a := make([]int, 3, 5)
a[0], a[1], a[2] = 1, 2, 3
appendInRoom(a)
fmt.Println("main里:", a, "len=", len(a), "cap=", cap(a))
// main 的 len 仍是 3,但 a[3] 位置可能已被写入 99(共享仓库时)
}
go
// 情况 B:仓库满了(cap 不够),append 会搬新仓库
func appendNewRoom(s []int) {
s = append(s, 99)
fmt.Println("函数里:", s)
}
func main() {
b := []int{1, 2, 3} // len=3, cap=3,满了
appendNewRoom(b)
fmt.Println("main里:", b) // 还是 [1 2 3],不会多出 99
}
情况 B 运行结果:
函数里: [1 2 3 99]
main里: [1 2 3]
读懂它:
- 仓库有空位 → 新货可能写在同一间仓库里,main 虽 len 没变,但共享区域的内容可能被动过(进阶细节,初学先记住「可能联动」)。
- 仓库满了要搬家 → 函数里的说明书改指向新仓库 ,main 仍指着旧仓库 ,所以 main 看不到新加的 99。
这就是为什么教科书说:append 的返回值一定要接回来 --- 搬家后必须用新说明书:
go
s = append(s, 99) // 对!
append(s, 99) // 错!搬家了你还拿着旧说明书
示例 3:函数里清空切片 --- main 完全不变
go
package main
import "fmt"
func clearInFunc(s []int) {
s = s[:0] // 只在函数内的说明书上写:当前 0 个元素
fmt.Println("函数里 len:", len(s))
}
func main() {
nums := []int{1, 2, 3}
clearInFunc(nums)
fmt.Println("main里:", nums, "len=", len(nums))
}
运行结果:
函数里 len: 0
main里: [1 2 3] len= 3
读懂它: s = s[:0] 改的是函数手里那份说明书上的 len,main 那份没动。
和数组对比:为什么数组「传了也不联动」?
go
package main
import "fmt"
func changeArray(arr [3]int) {
arr[0] = 999
}
func changeSlice(s []int) {
s[0] = 999
}
func main() {
a := [3]int{1, 2, 3}
changeArray(a)
fmt.Println("数组:", a) // [1 2 3] 不变
b := []int{1, 2, 3}
changeSlice(b)
fmt.Println("切片:", b) // [999 2 3] 变了
}
人话: 数组传参时,Go 把整间仓库连同货物复印一份 给函数,函数怎么改都不影响原来的。
切片只复印说明书,仓库还是同一间。
网上几种说法,哪个对?
| 你听到的说法 | 对不对 | 人话解释 |
|---|---|---|
| 「切片是引用类型」 | 容易误导 | 没有单独的「引用」对象;只是仓库共用,所以看起来像引用 |
| 「切片是值传递」 | 半对 | 传的是说明书(很小一份),不是全部货物 |
| 「切片是引用传递」 | 不对 | 不是传一个指针变量那么简单 |
| 建议记法 | ✅ | 复印说明书,共用仓库;改货互相见,换说明书互不影响 |
新手最常问的 4 个问题
Q1:那我传切片进函数,到底安不安全?
- 只读(看看数据、循环打印)→ 一般没问题。
- 要改里面的元素 → 调用方会受影响,调用前心里要有数。
- 要
append、要清空、要交给别的 goroutine → 最好slices.Clone拷一份,各用各的仓库。
Q2:和传 *[]int 指针有什么区别?
传指针是「把说明书的存放地址 告诉函数」,函数可以通过指针换掉 main 手里的整本说明书。日常写后端 传 []T 就够了,指针版反而容易绕晕。
Q3:后端里这知识用在哪?
Service 把查出来的 []Order 交给另一个函数过滤,若对方 append 或改元素,你的列表可能被动过 --- 不确定时就 Clone 一份再传。
Q4:还要不要背 ptr、len、cap?
要理解含义 ,不必背字节数。对应关系:门牌(从哪开始)+ 当前几格货(len)+ 还能放几格(cap)。
一张图总结(配合上面类比)
main 的 nums 函数参数 s
│ │
▼ ▼
┌─────────┐ ┌─────────┐
│ 说明书 A │ 传参 = 复印一份 → │ 说明书 B │
│ len cap │ │ len cap │
└────┬────┘ └────┬────┘
│ 起初指向同一间 │
└────────────┬───────────────────┘
▼
┌───────────┐
│ 仓 库 │ ← 真正存 [1,2,3...] 的地方
└───────────┘
改仓库里的数 → A、B 都「看见」
改 B 的 len → 只有 B 变,A 不变
append 搬新仓库 → B 指向新仓库,A 仍指向旧的
7.1 传参规则速查(对照示例记忆)
把上面三个示例浓缩成一张表,写代码时对照即可:
| 你在函数里做的事 | main 里的切片会变吗? | 对应示例 |
|---|---|---|
s[i] = 新值 |
会变(同一仓库) | 示例 1 |
append 且仓库还有空位 |
内容可能变,main 的 len 通常不变 | 示例 2A |
append 且仓库满了(扩容) |
不会多出元素(函数指着新仓库) | 示例 2B |
s = s[:0] 或 s = 别的切片 |
不会(只改了函数里的说明书) | 示例 3 |
完整可运行版(和 7.0 示例 1 相同,方便对照):
go
func modify(s []int) {
if len(s) > 0 {
s[0] = 100 // main 看得见
}
s = append(s, 9) // 扩不扩容,决定 main 能不能看见 9
}
func main() {
s := []int{1, 2, 3}
modify(s)
fmt.Println(s) // [100 2 3],或 cap 够时还可能共享写入
}
记住一句: 动仓库里的货 → 两边联动;动说明书(len、指向哪间仓库)→ 只影响函数里那份。
7.2 时序图:把 7.0 的仓库故事再串一遍
对照上一节的「说明书 + 仓库」类比看图(不认识的词可以忽略,看图里谁连着谁):
仓库(真正存数据) 函数(复印件 B) main(你手里的说明书 A) 仓库(真正存数据) 函数(复印件 B) main(你手里的说明书 A) #mermaid-svg-KGdcfNHr2JgVUORb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KGdcfNHr2JgVUORb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KGdcfNHr2JgVUORb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KGdcfNHr2JgVUORb .error-icon{fill:#552222;}#mermaid-svg-KGdcfNHr2JgVUORb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KGdcfNHr2JgVUORb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KGdcfNHr2JgVUORb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KGdcfNHr2JgVUORb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KGdcfNHr2JgVUORb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KGdcfNHr2JgVUORb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KGdcfNHr2JgVUORb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KGdcfNHr2JgVUORb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KGdcfNHr2JgVUORb .marker.cross{stroke:#333333;}#mermaid-svg-KGdcfNHr2JgVUORb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KGdcfNHr2JgVUORb p{margin:0;}#mermaid-svg-KGdcfNHr2JgVUORb .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KGdcfNHr2JgVUORb text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-KGdcfNHr2JgVUORb .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-KGdcfNHr2JgVUORb .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-KGdcfNHr2JgVUORb .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-KGdcfNHr2JgVUORb .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-KGdcfNHr2JgVUORb #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-KGdcfNHr2JgVUORb .sequenceNumber{fill:white;}#mermaid-svg-KGdcfNHr2JgVUORb #sequencenumber{fill:#333;}#mermaid-svg-KGdcfNHr2JgVUORb #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-KGdcfNHr2JgVUORb .messageText{fill:#333;stroke:none;}#mermaid-svg-KGdcfNHr2JgVUORb .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KGdcfNHr2JgVUORb .labelText,#mermaid-svg-KGdcfNHr2JgVUORb .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-KGdcfNHr2JgVUORb .loopText,#mermaid-svg-KGdcfNHr2JgVUORb .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-KGdcfNHr2JgVUORb .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-KGdcfNHr2JgVUORb .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-KGdcfNHr2JgVUORb .noteText,#mermaid-svg-KGdcfNHr2JgVUORb .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-KGdcfNHr2JgVUORb .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KGdcfNHr2JgVUORb .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KGdcfNHr2JgVUORb .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KGdcfNHr2JgVUORb .actorPopupMenu{position:absolute;}#mermaid-svg-KGdcfNHr2JgVUORb .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-KGdcfNHr2JgVUORb .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KGdcfNHr2JgVUORb .actor-man circle,#mermaid-svg-KGdcfNHr2JgVUORb line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-KGdcfNHr2JgVUORb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} A 和 B 起初指向同一间仓库 main 的 len 不变,但仓库内容可能被动 main 仍指着旧仓库,互不影响 alt 仓库还有空位(append 不搬家) 仓库满了(append 搬新仓库) A 完全不变 传参 = 复印说明书 B s0 = 100(改仓库里的货) main 也看见变了 append 写入新货 函数改去新仓库 s = s:0(只改 B 上的字数)
7.3 截取一片:两个切片看同一间仓库
a[2:4] 不是复制数据,而是新印一本说明书,门牌改从第 2 格算起,仓库还是同一间:
go
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5}
b := a[2:4] // b 看到的是 [3, 4]
fmt.Println("改之前 b:", b)
a[3] = 99 // 改的是仓库第 4 格
fmt.Println("改之后 b:", b) // [3, 99] --- b 也跟着变!
}
读懂它: a 和 b 不是两份独立数据,是两本指着同一仓库的说明书 。后端里分页截取 users[20:30] 时,若后面代码改动了共享区域,原列表也可能变 --- 所以需要 Clone 或三索引(第 4 章)。
7.4 内存滞留:只要 10 个,为何占着百万个的空间?
生活类比
你租了一间超大仓库 (100 万个格子),其实只用前 10 格。
你对外说「我只要这 10 格」,但合同(说明书)上仍绑着整间大仓库 。
在 Go 看来,只要还有人拿着这份合同,整间仓库都不能拆。
问题代码
go
func top10() []int {
all := make([]int, 1_000_000) // 租了超大仓库
// ... 往里面装数据
return all[:10] // 对外只要 10 个,但合同还绑着整间仓库
}
调用方以为只拿了 10 个整数,实际上暗中拽着 100 万元素的仓库,内存降不下来,服务跑久了可能越来越吃内存。
正确做法:把要的货搬到小仓库再交出去
go
func top10() []int {
all := make([]int, 1_000_000)
// ... 填充
out := make([]int, 10) // 新租一小间仓库
copy(out, all[:10]) // 只搬 10 个货过去
return out // 大仓库没人引用,可以回收
}
后端里的类似场景
go
body, _ := io.ReadAll(r.Body) // body 可能 10MB
log.Info(string(body[:200])) // 只想打 200 字节日志
// 若日志系统长期拿着 body[:200],等于 10MB 一时半会回收不了
// 更好:copy 到 make([]byte, 200) 再记日志
时序图(看懂「为何回收不了」即可)
垃圾回收 调用方 返回的 all:10 大仓库 100万格 函数 垃圾回收 调用方 返回的 all:10 大仓库 100万格 函数 #mermaid-svg-zhqbL7vA8L2pg2o5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zhqbL7vA8L2pg2o5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zhqbL7vA8L2pg2o5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zhqbL7vA8L2pg2o5 .error-icon{fill:#552222;}#mermaid-svg-zhqbL7vA8L2pg2o5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zhqbL7vA8L2pg2o5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zhqbL7vA8L2pg2o5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zhqbL7vA8L2pg2o5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zhqbL7vA8L2pg2o5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zhqbL7vA8L2pg2o5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zhqbL7vA8L2pg2o5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zhqbL7vA8L2pg2o5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zhqbL7vA8L2pg2o5 .marker.cross{stroke:#333333;}#mermaid-svg-zhqbL7vA8L2pg2o5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zhqbL7vA8L2pg2o5 p{margin:0;}#mermaid-svg-zhqbL7vA8L2pg2o5 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-zhqbL7vA8L2pg2o5 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-zhqbL7vA8L2pg2o5 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-zhqbL7vA8L2pg2o5 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-zhqbL7vA8L2pg2o5 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-zhqbL7vA8L2pg2o5 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-zhqbL7vA8L2pg2o5 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-zhqbL7vA8L2pg2o5 .sequenceNumber{fill:white;}#mermaid-svg-zhqbL7vA8L2pg2o5 #sequencenumber{fill:#333;}#mermaid-svg-zhqbL7vA8L2pg2o5 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-zhqbL7vA8L2pg2o5 .messageText{fill:#333;stroke:none;}#mermaid-svg-zhqbL7vA8L2pg2o5 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-zhqbL7vA8L2pg2o5 .labelText,#mermaid-svg-zhqbL7vA8L2pg2o5 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-zhqbL7vA8L2pg2o5 .loopText,#mermaid-svg-zhqbL7vA8L2pg2o5 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-zhqbL7vA8L2pg2o5 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-zhqbL7vA8L2pg2o5 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-zhqbL7vA8L2pg2o5 .noteText,#mermaid-svg-zhqbL7vA8L2pg2o5 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-zhqbL7vA8L2pg2o5 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-zhqbL7vA8L2pg2o5 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-zhqbL7vA8L2pg2o5 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-zhqbL7vA8L2pg2o5 .actorPopupMenu{position:absolute;}#mermaid-svg-zhqbL7vA8L2pg2o5 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-zhqbL7vA8L2pg2o5 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-zhqbL7vA8L2pg2o5 .actor-man circle,#mermaid-svg-zhqbL7vA8L2pg2o5 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-zhqbL7vA8L2pg2o5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 修复:copy 到只有 10 格的小仓库再返回 创建大仓库 截取前 10 格(说明书仍绑大仓库) 调用方拿着 Small 希望大仓库被拆掉 不行,Small 还指着大仓库门口
7.5 逃逸分析(可选拓展,初学可跳过)
「仓库」通常建在堆上(程序运行期间长期存在的地方),「说明书」可能临时放在栈上。
想知道编译器怎么决定的,可以运行(看不看得懂输出无所谓):
bash
go build -gcflags="-m" ./...
7.6 理解检验(用 7.0 的话回答)
-
函数里写
s[0] = 100,main 里的切片变吗?为什么?→ 变。改的是同一间仓库里的货,两份说明书都指着它。
-
函数里写
s = s[:0],main 里的 len 变吗?为什么?→ 不变。只改了函数手里那份说明书。
-
return big[:10]为什么可能导致内存很大?→ 说明书还绑着整间大仓库,垃圾回收拆不掉。
-
不确定对方会不会改你的列表,传参前该怎么办?
→
slices.Clone拷一份,各用各的仓库。
8. 后端相关类型:JSON 与 \[\]byte
本章从语言与标准库行为出发,帮助理解后端里常见现象,不是 API 设计规范。
8.1 encoding/json 与切片
go
type Resp struct {
Items []Item `json:"items"`
Tags []string `json:"tags,omitempty"`
}
var r Resp
// r.Items 为 nil → 序列化为 "items": null
// r.Items = []Item{} → "items": []
| 切片状态 | JSON 输出 |
|---|---|
nil |
null(字段存在时) |
[]T{} / make([]T,0) |
[] |
| 有元素 | [...] |
omitempty:字段为 nil、空切片、零值等时可能省略字段(需结合具体类型理解文档)。
学习点 :len==0 不能区分 nil 与空切片;需要时用 == nil 或统一初始化习惯。
8.2 时序图:JSON 与 nil / 空切片
序列化(Marshal)
HTTP Body json.Marshal Resp struct Handler HTTP Body json.Marshal Resp struct Handler #mermaid-svg-DdasH2GCZ3G446jH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DdasH2GCZ3G446jH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DdasH2GCZ3G446jH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DdasH2GCZ3G446jH .error-icon{fill:#552222;}#mermaid-svg-DdasH2GCZ3G446jH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DdasH2GCZ3G446jH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DdasH2GCZ3G446jH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DdasH2GCZ3G446jH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DdasH2GCZ3G446jH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DdasH2GCZ3G446jH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DdasH2GCZ3G446jH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DdasH2GCZ3G446jH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DdasH2GCZ3G446jH .marker.cross{stroke:#333333;}#mermaid-svg-DdasH2GCZ3G446jH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DdasH2GCZ3G446jH p{margin:0;}#mermaid-svg-DdasH2GCZ3G446jH .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-DdasH2GCZ3G446jH text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-DdasH2GCZ3G446jH .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-DdasH2GCZ3G446jH .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-DdasH2GCZ3G446jH .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-DdasH2GCZ3G446jH .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-DdasH2GCZ3G446jH #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-DdasH2GCZ3G446jH .sequenceNumber{fill:white;}#mermaid-svg-DdasH2GCZ3G446jH #sequencenumber{fill:#333;}#mermaid-svg-DdasH2GCZ3G446jH #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-DdasH2GCZ3G446jH .messageText{fill:#333;stroke:none;}#mermaid-svg-DdasH2GCZ3G446jH .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-DdasH2GCZ3G446jH .labelText,#mermaid-svg-DdasH2GCZ3G446jH .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-DdasH2GCZ3G446jH .loopText,#mermaid-svg-DdasH2GCZ3G446jH .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-DdasH2GCZ3G446jH .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-DdasH2GCZ3G446jH .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-DdasH2GCZ3G446jH .noteText,#mermaid-svg-DdasH2GCZ3G446jH .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-DdasH2GCZ3G446jH .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-DdasH2GCZ3G446jH .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-DdasH2GCZ3G446jH .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-DdasH2GCZ3G446jH .actorPopupMenu{position:absolute;}#mermaid-svg-DdasH2GCZ3G446jH .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-DdasH2GCZ3G446jH .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-DdasH2GCZ3G446jH .actor-man circle,#mermaid-svg-DdasH2GCZ3G446jH line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-DdasH2GCZ3G446jH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt Items == nil Items = \[Item{}] Items 有元素 var r Resp(零值) Items 为 nil "items": null Items = make(\[\]Item, 0) len=0, 非 nil "items": \[\] Items = {...}, ... "items": {...}
反序列化(Unmarshal)
req.IDs \[\]int64 json.Unmarshal 客户端 req.IDs \[\]int64 json.Unmarshal 客户端 #mermaid-svg-sNjGQhndpSvdTAiA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sNjGQhndpSvdTAiA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sNjGQhndpSvdTAiA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sNjGQhndpSvdTAiA .error-icon{fill:#552222;}#mermaid-svg-sNjGQhndpSvdTAiA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sNjGQhndpSvdTAiA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sNjGQhndpSvdTAiA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sNjGQhndpSvdTAiA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sNjGQhndpSvdTAiA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sNjGQhndpSvdTAiA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sNjGQhndpSvdTAiA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sNjGQhndpSvdTAiA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sNjGQhndpSvdTAiA .marker.cross{stroke:#333333;}#mermaid-svg-sNjGQhndpSvdTAiA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sNjGQhndpSvdTAiA p{margin:0;}#mermaid-svg-sNjGQhndpSvdTAiA .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sNjGQhndpSvdTAiA text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-sNjGQhndpSvdTAiA .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-sNjGQhndpSvdTAiA .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-sNjGQhndpSvdTAiA .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-sNjGQhndpSvdTAiA .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-sNjGQhndpSvdTAiA #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-sNjGQhndpSvdTAiA .sequenceNumber{fill:white;}#mermaid-svg-sNjGQhndpSvdTAiA #sequencenumber{fill:#333;}#mermaid-svg-sNjGQhndpSvdTAiA #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-sNjGQhndpSvdTAiA .messageText{fill:#333;stroke:none;}#mermaid-svg-sNjGQhndpSvdTAiA .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sNjGQhndpSvdTAiA .labelText,#mermaid-svg-sNjGQhndpSvdTAiA .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-sNjGQhndpSvdTAiA .loopText,#mermaid-svg-sNjGQhndpSvdTAiA .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-sNjGQhndpSvdTAiA .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-sNjGQhndpSvdTAiA .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-sNjGQhndpSvdTAiA .noteText,#mermaid-svg-sNjGQhndpSvdTAiA .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-sNjGQhndpSvdTAiA .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sNjGQhndpSvdTAiA .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sNjGQhndpSvdTAiA .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sNjGQhndpSvdTAiA .actorPopupMenu{position:absolute;}#mermaid-svg-sNjGQhndpSvdTAiA .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-sNjGQhndpSvdTAiA .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sNjGQhndpSvdTAiA .actor-man circle,#mermaid-svg-sNjGQhndpSvdTAiA line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-sNjGQhndpSvdTAiA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 保持零值 nil 结果为 nil "ids": 1, 2, 3 分配切片,len=3 "ids": \[\] 分配空切片,len=0,非 nil 省略 ids 字段 "ids": null
8.3 反序列化
go
var req struct {
IDs []int64 `json:"ids"`
}
json.Unmarshal(data, &req)
| JSON | 结果 req.IDs |
|---|---|
"ids":[1,2,3] |
非 nil,有元素 |
"ids":[] |
非 nil,len=0 |
省略 ids |
nil |
"ids":null |
nil |
8.4 \[\]byte 与 string
go
b := []byte("hello") // 分配新数组,拷贝
s := string(b) // 拷贝
后端中 []byte 常见于:读 HTTP body、哈希、加解密。学习阶段记住:
- 标准转换会拷贝(安全)
- 大量拼接用
strings.Builder,而不是反复[]byte+string转换 - 从大
[]byte取子切片保存时,注意第 7 章的内存滞留
8.5 理解检验
- 想让 JSON 里列表字段始终是
[]而不是null,初始化应怎么做?([]T{}或make([]T,0)) len(b)==0能判断b==nil吗?(不能)
9. 并发环境下的切片
9.1 核心原则
多个 goroutine 不能无保护地同时写同一切片 (包括 append),否则 data race。
go
// 错误示例:学习识别即可
var results []Result
for _, id := range ids {
go func(id int64) {
r, _ := fetch(id)
results = append(results, r) // race
}(id)
}
9.2 时序图:并发 append 的 data race
错误写法 --- 多 goroutine 同时 append 同一切片:
race detector results \[\]Result goroutine 2 goroutine 1 race detector results \[\]Result goroutine 2 goroutine 1 #mermaid-svg-5MZoZ5TffCTMYBcZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5MZoZ5TffCTMYBcZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5MZoZ5TffCTMYBcZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5MZoZ5TffCTMYBcZ .error-icon{fill:#552222;}#mermaid-svg-5MZoZ5TffCTMYBcZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5MZoZ5TffCTMYBcZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5MZoZ5TffCTMYBcZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5MZoZ5TffCTMYBcZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5MZoZ5TffCTMYBcZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5MZoZ5TffCTMYBcZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5MZoZ5TffCTMYBcZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5MZoZ5TffCTMYBcZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5MZoZ5TffCTMYBcZ .marker.cross{stroke:#333333;}#mermaid-svg-5MZoZ5TffCTMYBcZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5MZoZ5TffCTMYBcZ p{margin:0;}#mermaid-svg-5MZoZ5TffCTMYBcZ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5MZoZ5TffCTMYBcZ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-5MZoZ5TffCTMYBcZ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5MZoZ5TffCTMYBcZ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-5MZoZ5TffCTMYBcZ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-5MZoZ5TffCTMYBcZ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-5MZoZ5TffCTMYBcZ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-5MZoZ5TffCTMYBcZ .sequenceNumber{fill:white;}#mermaid-svg-5MZoZ5TffCTMYBcZ #sequencenumber{fill:#333;}#mermaid-svg-5MZoZ5TffCTMYBcZ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-5MZoZ5TffCTMYBcZ .messageText{fill:#333;stroke:none;}#mermaid-svg-5MZoZ5TffCTMYBcZ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5MZoZ5TffCTMYBcZ .labelText,#mermaid-svg-5MZoZ5TffCTMYBcZ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-5MZoZ5TffCTMYBcZ .loopText,#mermaid-svg-5MZoZ5TffCTMYBcZ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-5MZoZ5TffCTMYBcZ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5MZoZ5TffCTMYBcZ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-5MZoZ5TffCTMYBcZ .noteText,#mermaid-svg-5MZoZ5TffCTMYBcZ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-5MZoZ5TffCTMYBcZ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5MZoZ5TffCTMYBcZ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5MZoZ5TffCTMYBcZ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5MZoZ5TffCTMYBcZ .actorPopupMenu{position:absolute;}#mermaid-svg-5MZoZ5TffCTMYBcZ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-5MZoZ5TffCTMYBcZ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5MZoZ5TffCTMYBcZ .actor-man circle,#mermaid-svg-5MZoZ5TffCTMYBcZ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-5MZoZ5TffCTMYBcZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} par 并发写入 append(results, r1) append(results, r2) 同时读写 len/ptr/底层数组 WARNING: data race
安全写法 --- 预分配 + 各写各的下标:
resultslen(ids) goroutine i=1 goroutine i=0 main resultslen(ids) goroutine i=1 goroutine i=0 main #mermaid-svg-Tzg51NJLoRINkdXL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Tzg51NJLoRINkdXL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Tzg51NJLoRINkdXL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Tzg51NJLoRINkdXL .error-icon{fill:#552222;}#mermaid-svg-Tzg51NJLoRINkdXL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Tzg51NJLoRINkdXL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Tzg51NJLoRINkdXL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Tzg51NJLoRINkdXL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Tzg51NJLoRINkdXL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Tzg51NJLoRINkdXL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Tzg51NJLoRINkdXL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Tzg51NJLoRINkdXL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Tzg51NJLoRINkdXL .marker.cross{stroke:#333333;}#mermaid-svg-Tzg51NJLoRINkdXL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Tzg51NJLoRINkdXL p{margin:0;}#mermaid-svg-Tzg51NJLoRINkdXL .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Tzg51NJLoRINkdXL text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Tzg51NJLoRINkdXL .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Tzg51NJLoRINkdXL .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Tzg51NJLoRINkdXL .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Tzg51NJLoRINkdXL .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Tzg51NJLoRINkdXL #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Tzg51NJLoRINkdXL .sequenceNumber{fill:white;}#mermaid-svg-Tzg51NJLoRINkdXL #sequencenumber{fill:#333;}#mermaid-svg-Tzg51NJLoRINkdXL #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Tzg51NJLoRINkdXL .messageText{fill:#333;stroke:none;}#mermaid-svg-Tzg51NJLoRINkdXL .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Tzg51NJLoRINkdXL .labelText,#mermaid-svg-Tzg51NJLoRINkdXL .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Tzg51NJLoRINkdXL .loopText,#mermaid-svg-Tzg51NJLoRINkdXL .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Tzg51NJLoRINkdXL .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Tzg51NJLoRINkdXL .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Tzg51NJLoRINkdXL .noteText,#mermaid-svg-Tzg51NJLoRINkdXL .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Tzg51NJLoRINkdXL .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Tzg51NJLoRINkdXL .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Tzg51NJLoRINkdXL .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Tzg51NJLoRINkdXL .actorPopupMenu{position:absolute;}#mermaid-svg-Tzg51NJLoRINkdXL .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Tzg51NJLoRINkdXL .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Tzg51NJLoRINkdXL .actor-man circle,#mermaid-svg-Tzg51NJLoRINkdXL line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Tzg51NJLoRINkdXL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} par 各写独立槽位 无 append,无共享写 保序且线程安全 make(\[\]Result, len(ids)) results0 = r0 results1 = r1
9.3 安全思路(理解不同方案)
| 思路 | 做法 | 适用学习场景 |
|---|---|---|
| 互斥锁 | mu.Lock(); append; mu.Unlock() |
理解「共享写要同步」 |
| 预分配 + 下标 | out[i] = r,每个 i 只写一个槽位 |
理解「避免共享 append」 |
| 通道收集 | 各 goroutine 发送到 channel,单 goroutine 汇总 | 理解「所有权转移」 |
| 局部合并 | 每个 worker 自己的切片,最后 append 合并 |
理解「减少共享」 |
go
// 预分配 + 下标(保序)
results := make([]Result, len(ids))
for i, id := range ids {
i, id := i, id
go func() {
results[i] = fetch(id) // 每个 i 唯一,无 append race
}()
}
9.4 时序图:向 goroutine 传递切片
主 goroutine 若继续修改 orders,子 goroutine 应使用克隆副本:
orders 底层数组 goroutine slices.Clone main goroutine orders 底层数组 goroutine slices.Clone main goroutine #mermaid-svg-CPY8kU8Nj25YRTj3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CPY8kU8Nj25YRTj3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CPY8kU8Nj25YRTj3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CPY8kU8Nj25YRTj3 .error-icon{fill:#552222;}#mermaid-svg-CPY8kU8Nj25YRTj3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CPY8kU8Nj25YRTj3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CPY8kU8Nj25YRTj3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CPY8kU8Nj25YRTj3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CPY8kU8Nj25YRTj3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CPY8kU8Nj25YRTj3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CPY8kU8Nj25YRTj3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CPY8kU8Nj25YRTj3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CPY8kU8Nj25YRTj3 .marker.cross{stroke:#333333;}#mermaid-svg-CPY8kU8Nj25YRTj3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CPY8kU8Nj25YRTj3 p{margin:0;}#mermaid-svg-CPY8kU8Nj25YRTj3 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-CPY8kU8Nj25YRTj3 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-CPY8kU8Nj25YRTj3 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-CPY8kU8Nj25YRTj3 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-CPY8kU8Nj25YRTj3 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-CPY8kU8Nj25YRTj3 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-CPY8kU8Nj25YRTj3 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-CPY8kU8Nj25YRTj3 .sequenceNumber{fill:white;}#mermaid-svg-CPY8kU8Nj25YRTj3 #sequencenumber{fill:#333;}#mermaid-svg-CPY8kU8Nj25YRTj3 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-CPY8kU8Nj25YRTj3 .messageText{fill:#333;stroke:none;}#mermaid-svg-CPY8kU8Nj25YRTj3 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-CPY8kU8Nj25YRTj3 .labelText,#mermaid-svg-CPY8kU8Nj25YRTj3 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-CPY8kU8Nj25YRTj3 .loopText,#mermaid-svg-CPY8kU8Nj25YRTj3 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-CPY8kU8Nj25YRTj3 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-CPY8kU8Nj25YRTj3 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-CPY8kU8Nj25YRTj3 .noteText,#mermaid-svg-CPY8kU8Nj25YRTj3 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-CPY8kU8Nj25YRTj3 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-CPY8kU8Nj25YRTj3 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-CPY8kU8Nj25YRTj3 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-CPY8kU8Nj25YRTj3 .actorPopupMenu{position:absolute;}#mermaid-svg-CPY8kU8Nj25YRTj3 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-CPY8kU8Nj25YRTj3 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-CPY8kU8Nj25YRTj3 .actor-man circle,#mermaid-svg-CPY8kU8Nj25YRTj3 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-CPY8kU8Nj25YRTj3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 可能 data race 或数据不一致 alt 直接传 orders(有风险) 传 Clone(推荐学习模型) orders 在 main 中 go process(orders) 同时 append / 修改 读取或 append slices.Clone(orders) 独立底层数组 main 继续修改 仅操作副本,互不影响
9.5 传给 goroutine 的参数
若主 goroutine 仍可能修改 s,子 goroutine 应使用 拷贝 或 只读子切片且不再写 s:
go
go func(batch []Order) {
process(batch)
}(slices.Clone(orders))
9.6 sync.Pool 与 \[\]byte(拓展)
长驻服务常用 sync.Pool 复用 []byte 缓冲。学习点 :取出后当次使用,归还前 buf = buf[:0];不要把 pool 里的切片泄露到外部长期持有。
9.7 理解检验
- 为何「每个 goroutine 写不同下标」通常比「共同 append」更安全?(各写各的槽位,无共享写)
go test -race的作用?(检测 data race)
10. 知识串联与综合练习
10.1 概念关系图
底层数组
▲
┌───────────┴───────────┐
│ slice header │
│ ptr + len + cap │
└───────────┬───────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
切片表达式 append 传参/共享
[low:high:max] 扩容/预分配 函数、goroutine
│ │ │
└────────────────┴────────────────┘
│
后端语境中的体现
JSON、查询列表、[]byte、并发
10.2 时序图:从查询到响应(综合串联)
情景 :分页查用户,每页 10 条,无数据时返回 [] 而非 null。
json.Marshal 数据库 Service Handler 客户端 json.Marshal 数据库 Service Handler 客户端 #mermaid-svg-6IAns56ojmVFBrs1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6IAns56ojmVFBrs1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6IAns56ojmVFBrs1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6IAns56ojmVFBrs1 .error-icon{fill:#552222;}#mermaid-svg-6IAns56ojmVFBrs1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6IAns56ojmVFBrs1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6IAns56ojmVFBrs1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6IAns56ojmVFBrs1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6IAns56ojmVFBrs1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6IAns56ojmVFBrs1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6IAns56ojmVFBrs1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6IAns56ojmVFBrs1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6IAns56ojmVFBrs1 .marker.cross{stroke:#333333;}#mermaid-svg-6IAns56ojmVFBrs1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6IAns56ojmVFBrs1 p{margin:0;}#mermaid-svg-6IAns56ojmVFBrs1 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6IAns56ojmVFBrs1 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-6IAns56ojmVFBrs1 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6IAns56ojmVFBrs1 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-6IAns56ojmVFBrs1 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-6IAns56ojmVFBrs1 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-6IAns56ojmVFBrs1 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-6IAns56ojmVFBrs1 .sequenceNumber{fill:white;}#mermaid-svg-6IAns56ojmVFBrs1 #sequencenumber{fill:#333;}#mermaid-svg-6IAns56ojmVFBrs1 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-6IAns56ojmVFBrs1 .messageText{fill:#333;stroke:none;}#mermaid-svg-6IAns56ojmVFBrs1 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6IAns56ojmVFBrs1 .labelText,#mermaid-svg-6IAns56ojmVFBrs1 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-6IAns56ojmVFBrs1 .loopText,#mermaid-svg-6IAns56ojmVFBrs1 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-6IAns56ojmVFBrs1 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6IAns56ojmVFBrs1 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-6IAns56ojmVFBrs1 .noteText,#mermaid-svg-6IAns56ojmVFBrs1 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-6IAns56ojmVFBrs1 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6IAns56ojmVFBrs1 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6IAns56ojmVFBrs1 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6IAns56ojmVFBrs1 .actorPopupMenu{position:absolute;}#mermaid-svg-6IAns56ojmVFBrs1 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-6IAns56ojmVFBrs1 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6IAns56ojmVFBrs1 .actor-man circle,#mermaid-svg-6IAns56ojmVFBrs1 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-6IAns56ojmVFBrs1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop rows.Scan alt 有数据 无数据 若返回 nil 切片 → JSON null 契约要求 \[\] 时需 \[\]UserDTO{} GET /users?page=1&size=10 List(page=1, size=10) list = make(\[\]UserDTO, 0, 10) SELECT ... LIMIT 10 一行 list = append(list, dto) 0 行 list 保持 len=0(非 nil) \[\]UserDTO Marshal(list) {"list":\[\]} 或 {"list":...} 200 OK
10.3 串联推演:从查询到响应
情景:分页查用户,每页 10 条,结果 JSON 返回。
按学习顺序自问自答:
- 应用什么创建方式?→
make([]UserDTO, 0, 10) - 循环
rows.Scan后如何收集?→list = append(list, dto) - 查不到数据时返回什么?→ 若契约要
[],用[]UserDTO{},不是 nil - 若把
list的一部分交给异步任务?→Clone或拷贝 - 若从 10MB body 只取前 512 字节记日志?→ 注意子切片滞留,应
copy到小缓冲
10.4 练习题
练习 1 :以下 len、cap 各是多少?
go
a := make([]int, 5, 10)
b := a[2:5]
c := a[2:5:7]
参考答案
b:len=3, cap=8c:len=3, cap=5
练习 2:输出什么?
go
s := []int{1, 2, 3}
s2 := append(s[:2], 10)
fmt.Println(s, s2)
参考答案
[1 2 10] [1 2 10] --- 共享底层数组,append 覆盖原 s[2]。
练习 3:下面函数有什么问题?如何改?
go
func Handler(w http.ResponseWriter, r *http.Request) {
buf, _ := io.ReadAll(r.Body) // 可能很大
log.Info(string(buf[:200])) // 只打前 200 字节
}
参考答案
buf[:200] 仍引用整个 buf 底层数组,若 log 或异步路径长期持有,大 body 无法及时 GC。可 copy 到 make([]byte, 200),或用 io.LimitReader 控制读取量。
练习 4:设计题(不写完整项目,只写思路)
并发拉取 50 个 URL 的标题,存入 []string,如何保证无 data race 且尽量保序?
参考思路
titles := make([]string, 50),第 i 个 goroutine 只写 titles[i];或用 errgroup + 下标赋值。
11. 附录:速查与延伸阅读
11.1 常用操作速查
| 需求 | 写法 |
|---|---|
| 预分配 | make([]T, 0, n) |
| 克隆 | slices.Clone(s) |
| 限制 cap 的子切片 | s[low:high:high] |
| 清空复用 | s = s[:0] |
| 判等 | slices.Equal(a, b) |
| 业务判空 | len(s) == 0 |
| 合并 | append(a, b...) |
11.2 常见误区(复习用)
| 误区 | 正解(人话) |
|---|---|
| 切片是引用类型,传参就会改 len | 只复印了说明书;改 len 只动函数里那份 |
len==0 就是 nil |
空盒子也可能是「有盒子但里面没货」([]T{}) |
append 不用赋值 |
搬家后必须接新说明书:s = append(s, x) |
| 子切片很省内存 | 可能暗中拽着整间大仓库(见 7.4) |
| 值拷贝还是引用拷贝? | 复印说明书,共用仓库 --- 两个词都不完全对 |
11.3 延伸阅读
- Go 语言规范 - Slice types
- Go blog: Go slices: usage and internals
- Effective Go - Slices
- 源码:
runtime/slice.go中的growslice