Go Slice 详解

Go Slice 详解

一、Slice 是什么

Slice(切片)是 Go 中对数组的轻量级抽象 ,本质是一个引用类型的描述符,包含三个字段:

复制代码
┌─────────────┬──────┬──────┐
│    ptr      │ len  │ cap  │
│  *array     │  int │  int │
└─────────────┴──────┴──────┘
  • ptr --- 指向底层数组的指针
  • len --- 当前元素个数(可读写的范围)
  • cap --- 底层数组从 ptr 开始到末尾的容量
go 复制代码
s := make([]int, 3, 5)
// ptr -> [0, 0, 0, _, _]
// len = 3, cap = 5

二、创建方式

go 复制代码
// 1. 字面量
s1 := []int{1, 2, 3}

// 2. make
s2 := make([]int, 3)       // len=3, cap=3, 零值
s3 := make([]int, 3, 10)   // len=3, cap=10

// 3. 从数组/切片切取
arr := [5]int{0, 1, 2, 3, 4}
s4 := arr[1:3]  // [1, 2], len=2, cap=4 (从index1到数组末尾)

// 4. new --- 不推荐,得到的是 *[]int,且 len/cap 都为 0
s5 := new([]int)

三、扩容机制(核心原理)

append 导致 len > cap 时,Go 会分配更大的底层数组并拷贝数据:

go 复制代码
s := make([]int, 0, 2)
s = append(s, 1)   // [1], cap=2
s = append(s, 2)   // [1,2], cap=2 --- 刚好满
s = append(s, 3)   // [1,2,3], cap=4 --- 触发扩容,ptr 已变!

扩容策略(Go 1.18+)

复制代码
newcap = oldcap

如果 oldcap < 256:
    newcap += newcap  (翻倍)
如果 oldcap >= 256:
    newcap += newcap * 3/4  (每次增长 25%)

// 再考虑元素大小和内存对齐做微调

关键点:扩容后 ptr 指向新数组,旧数组等待 GC。


四、常见操作

增删改查

go 复制代码
// 追加
s = append(s, 4)

// 批量追加
s = append(s, []int{5, 6, 7}...)

// 插入到索引 i
s = append(s[:i], append([]int{x}, s[i:]...)...)

//插入可以看下面三种方式
// 方法 1:三行展开,逻辑清晰
func insert(s []int, i int, x int) []int {
 tail := append([]int{x}, s[i:]...) // [x, 原i及之后的所有元素]
 return append(s[:i], tail...) // [原i之前, x, 原i及之后]
}

// 方法 2:append + copy(Go Wiki 推荐,最清晰)
func insert(s []int, i int, x int) []int {
 s = append(s, 0) // 先扩一个位置
 copy(s[i+1:], s[i:]) // 把 i 及之后的内容整体右移一位
 s[i] = x // 在 i 位置放入 x
 return s
}

// 方法 3:Go 1.21+ slices 包
import "slices"
s = slices.Insert(s, i, x) // 直接用标准库

// 删除索引 i
s = append(s[:i], s[i+1:]...)

// 删除范围 [i, j)
s = append(s[:i], s[j:]...)

拷贝

go 复制代码
dst := make([]int, len(src))
copy(dst, src)  // 内置函数,处理了重叠内存

copy 返回实际拷贝的元素数 = min(len(dst), len(src))
如果 dst 比 src 短,只拷贝前面部分,不会 panic
copy 内部自动处理了重叠内存(类似 C 的 memmove,不是 memcpy),所以同一个 slice 内拷贝是安全的

为什么安全:memmove 会先判断重叠方向,必要时从后往前拷贝,避免数据还没读就被覆盖。

copy 是 Go 里做 slice 深拷贝的唯一内置方式,等价于 memmove(处理重叠),拷贝数量 = min(len(dst), len(src)),类型必须严格匹配。

和赋值的区别
a := s // 只拷贝 slice 头部(24 字节),共享底层数组
b := make([]int, len(s))
copy(b, s) // 深拷贝,独立的底层数组


// 截断(原地缩容,不改变 cap)
s = s[:n]
只是改了 len,cap 不变,底层数组不变
被截掉的部分还在内存中(cap 保留),不会立即释放
指针不动,只是 header 的 len 字段变小了

切片表达式

go 复制代码
a := [5]int{0, 1, 2, 3, 4}
s1 := a[1:3]     // [1, 2], len=2, cap=4
s2 := a[1:3:3]   // [1, 2], len=2, cap=2  ← 3-index slice

3-index slice a[low:high:max] --- 显式限制 cap,防止通过 append 意外修改原数组后续元素。Go 1.2+ 支持。


五、经典陷阱

1. 共享底层数组

go 复制代码
a := []int{1, 2, 3, 4, 5}
b := a[1:3]  // b -> [2, 3]

b[0] = 99
fmt.Println(a) // [1, 99, 3, 4, 5] --- a 也变了!

2. append 未捕获返回值

go 复制代码
s := []int{1, 2, 3}
append(s, 4)  // ❌ 丢弃返回值,如果触发扩容则完全无效
s = append(s, 4)  // ✅ 必须接收

3. 切片作函数参数 --- 修改可见

go 复制代码
func modify(s []int) {
    s[0] = 999  // 影响原切片
    s = append(s, 4)  // 如果扩容了,外部看不到
}

4. 大切片导致内存泄漏

go 复制代码
// 从大切片取一小段,底层数组不会被回收
var big = make([]byte, 1<<30)
small := big[:100]

// 解决:手动拷贝
small := make([]byte, 100)
copy(small, big[:100])

六、Slice vs Array 对比

Array Slice
类型签名 [3]int --- 长度是类型的一部分 []int --- 长度不固定
值语义 拷贝整个数组 拷贝的是描述符(24字节)
可否 == 比较 可以(同类型同长度) 不可以 ,只能和 nil
零值 元素零值 nil(len=0, cap=0, ptr=nil)
长度 编译期确定 运行时动态

七、最佳实践

  1. 预知大小时用 make([]T, 0, n) --- 避免多次扩容
  2. 需要传值语义时用 copy,不要依赖切片共享
  3. var s []int vs s := []int{} --- 前者是 nil,后者是空切片(非 nil),json.Marshal 行为不同:nilnull[][]
  4. 字符串转 []byte 会拷贝,频繁操作用 unsafe.Stringstrings.Builder
  5. 3-index slice 在从数组切取子切片时优先使用,防止意外覆盖
相关推荐
Jasmine_llq4 小时前
《B3867 [GESP202309 三级] 小杨的储蓄》
算法·循环遍历·数组累加(模拟)·索引定位·顺序输出
啦啦啦_99994 小时前
案例之 逻辑回归_电信用户流失预测
算法·机器学习·逻辑回归
风筝在晴天搁浅5 小时前
快手/字节 CodeTop LeetCode 415.字符串相加
算法·leetcode
DragonnAi5 小时前
猫咪如厕检测与分类识别系统系列【十四】 项目结构重新整理-即将开源完整算法
算法·开源
机器视觉_Explorer5 小时前
【halcon】编程技巧:鼠标擦除
图像处理·人工智能·深度学习·算法·视觉检测
灵智实验室6 小时前
PX4状态估计技术EKF2详解(二):EKF2 误差状态动力学与协方差传播
算法·无人机·px 4
米粒17 小时前
力扣算法刷题 Day 64 Floyd算法 & A* 算法 & 总结篇
算法·leetcode·职场和发展
XX風7 小时前
OpenGL中Face culling 面剔除的具体实现
算法·图形渲染
IT猿手7 小时前
光伏模型参数估计:基于山羊优化算法(GOA )的光伏模型参数辨识问题求解研究,免费提供完整MATLAB代码链接
开发语言·算法·matlab·群智能优化算法·智能优化算法·光伏模型参数估计·光伏模型参数辨识