Go切片学习记录


目录

  1. 切片在后端中扮演什么角色
  2. [底层结构:len、cap 与底层数组](#底层结构:len、cap 与底层数组)
  3. [创建、初始化与 nil 切片](#创建、初始化与 nil 切片)
  4. 切片表达式:截取与三索引
  5. [append 与扩容机制](#append 与扩容机制)
  6. copy、克隆与元素删除
  7. 函数传参、共享与内存滞留
  8. [后端相关类型:JSON 与 \[\]byte](#后端相关类型:JSON 与 []byte)
  9. 并发环境下的切片
  10. 知识串联与综合练习
  11. 附录:速查与延伸阅读

1. 切片在后端中扮演什么角色

1.1 核心概念

切片(slice)是对底层数组的视图,由三部分描述:

go 复制代码
// 概念模型,帮助理解内存布局(非标准库导出类型)
type slice struct {
    ptr *T   // 指向底层数组中某个元素
    len int  // 当前「逻辑长度」
    cap int  // 从 ptr 起到底层数组末尾的容量
}

后端代码里,切片是最常用的「变长集合」:

  • 查询结果:[]User[]Order
  • 请求参数:ids []int64tags []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 理解检验

  1. 切片本身存储元素吗?(否,存的是 ptr + len + cap)
  2. []User[10]User 能互相赋值吗?(不能,类型不同)

2. 底层结构:len、cap 与底层数组

2.1 len 与 cap 的含义

  • len :当前能访问的元素个数,合法下标 0len-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 理解检验

  1. make([]int, 5, 10) 的 len 和 cap 各是多少?(5 和 10)
  2. 能访问 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 理解检验

  1. var ids []int64 后直接 append(ids, 1) 合法吗?(合法)
  2. 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 理解检验

  1. s[1:4:4] 的 cap 是多少?(3)
  2. 什么时候应优先考虑三索引或 Clone?(子切片会交给可能 append 的代码,且不能与父切片共享尾部空间时)

5. append 与扩容机制

5.1 基本规则

go 复制代码
s = append(s, x)        // 必须接收返回值
s = append(s, a, b, c)
s = append(s, other...) // 展开另一个切片

append 可能:

  1. 原地扩展 lenlen+n <= cap)→ 仍共享原底层数组
  2. 分配新数组、拷贝、返回新 headerlen+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 理解检验

  1. append(s, 1) 而不赋值给 s,有什么问题?(可能丢元素;且 s 仍指向旧 header)
  2. 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 理解检验

  1. copyappend([]T(nil), s...) 都能复制,区别是什么场景?(copy 需已知 len 的 dst;append/Clone 更常用于得一个全新切片)
  2. 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 也跟着变!
}

读懂它: ab 不是两份独立数据,是两本指着同一仓库的说明书 。后端里分页截取 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 的话回答)

  1. 函数里写 s[0] = 100,main 里的切片变吗?为什么?

    。改的是同一间仓库里的货,两份说明书都指着它。

  2. 函数里写 s = s[:0],main 里的 len 变吗?为什么?

    不变。只改了函数手里那份说明书。

  3. return big[:10] 为什么可能导致内存很大?

    → 说明书还绑着整间大仓库,垃圾回收拆不掉。

  4. 不确定对方会不会改你的列表,传参前该怎么办?

    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 理解检验

  1. 想让 JSON 里列表字段始终是 [] 而不是 null,初始化应怎么做?([]T{}make([]T,0)
  2. 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 理解检验

  1. 为何「每个 goroutine 写不同下标」通常比「共同 append」更安全?(各写各的槽位,无共享写)
  2. 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 返回。

按学习顺序自问自答:

  1. 应用什么创建方式?→ make([]UserDTO, 0, 10)
  2. 循环 rows.Scan 后如何收集?→ list = append(list, dto)
  3. 查不到数据时返回什么?→ 若契约要 [],用 []UserDTO{},不是 nil
  4. 若把 list 的一部分交给异步任务?→ Clone 或拷贝
  5. 若从 10MB body 只取前 512 字节记日志?→ 注意子切片滞留,应 copy 到小缓冲

10.4 练习题

练习 1 :以下 lencap 各是多少?

go 复制代码
a := make([]int, 5, 10)
b := a[2:5]
c := a[2:5:7]

参考答案

  • b:len=3, cap=8
  • c: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。可 copymake([]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 延伸阅读