切片传参不加因[]T本身是轻量引用类型,仅复制24字节头信息;加反而引发逃逸、降低可读性;唯一例外是需修改len/cap本身。

切片传参为什么不用加 *?
因为 []T 本身就是轻量级引用类型:它只包含一个指针、一个长度和一个容量(共 24 字节),函数传参时只复制这三个字段,不拷贝底层数组。加 *[]T 反而多一层间接访问,还可能把切片头本身推到堆上(逃逸),得不偿失。
常见错误是看到“指针高效”就盲目套用:func process(s *[]byte) —— 这不仅没提速,还让调用方必须取地址(&buf),破坏了切片天然的可读性和安全性。
- ✅ 正确做法:直接传
[]byte,函数内用s = s[:n]或append修改内容(只要不扩容,就仍在原底层数组) - ❌ 错误信号:编译器提示
escape analysis显示切片头逃逸,或压测发现 cache miss 上升 - ⚠️ 唯一例外:你需要在函数内修改切片头三要素本身(比如想让调用方看到新的
len或cap改变),但这种情况极少,通常说明设计有误
[]MyStruct 和 []*MyStruct 性能差多少?
取决于结构体大小和操作频率。以一个含 7 个 string + 7 个 int64 的典型结构体为例,单个值约 168 字节;追加 100 万次,[]MyStruct 耗时约 3.5ms,[]*MyStruct 仅需 0.25ms —— 差 14 倍。
这不是“指针快”,而是避免了每次 append 都复制上百字节。但代价是:每个元素都是堆分配对象,GC 压力上升;且访问 s[i].Field 多一次内存跳转。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 推荐用
[]*MyStruct:当结构体 > 64 字节、切片长度常达万级以上、且频繁append/sort/for range - ✅ 推荐用
[]MyStruct:结构体小(如type Point struct{ X, Y int })、生命周期短、或需 CPU 缓存友好(如图像像素处理) - ⚠️ 注意:
sort.Slice对指针切片排序更快,但json.Marshal对值切片更省内存(无指针间接层)
为什么从大数组截取切片容易导致内存泄漏?
因为切片持有对底层数组的指针。比如你有一个 100MB 的 [100_000_000]byte,只取前 10 个字节做 s := bigArr[:10],只要 s 还活着,整个 100MB 就不会被 GC 回收。
这不是 bug,是 Go 的设计使然 —— 共享底层数组本就是切片高效的基础。问题出在“意外长生命周期持有”。
- ✅ 解决方案一(推荐):用
copy拷贝出独立小切片:safe := make([]byte, 10); copy(safe, bigArr[:10]) - ✅ 解决方案二:改用指针切片,如
[]*byte,但注意这不能解决原始数组占用,只是转移引用粒度 - ❌ 不要依赖
bigArr = nil:只要还有任意切片指向它,数组就不会释放
new([]T) 和 make([]T, 0, n) 到底有什么区别?
new([]T) 返回的是 *[]T,即一个指向 nil 切片的指针;make([]T, 0, n) 返回的是可用的、零长度但有容量的切片 []T。前者几乎没用,后者是复用内存的标准姿势。
典型错误:var s = new([]int); *s = append(*s, 1) —— 看似可行,实则每次 append 都触发扩容,且 *s 是堆变量,增加 GC 开销。
- ✅ 正确预分配复用:用
make([]int, 0, 1024)创建可重用切片,后续用s = s[:0]清空长度,不重建底层数组 - ✅ sync.Pool 场景:定义
var pool = sync.Pool{New: func() interface{} { return make([]byte, 0, 1024) }},获取后直接buf = buf[:0] - ⚠️
new([]T)唯一合理用途:作为结构体字段的初始零值占位符(极少见),不要用于逻辑初始化
最常被忽略的一点:切片的“共享性”不是特性,而是责任。你写 s[i] = x 时,永远要问一句——这个 s 还有别人在用吗?










