Go的append只在len(s)==cap(s)时触发扩容,此时按分段策略(cap<1024则翻倍,≥1024则增1.25倍并8字节对齐)分配新底层数组并拷贝数据;空slice首次append直接分配1个元素空间。

Go 的 append 不是每次调用都扩容,只在 len(s) == cap(s) 时触发——这是唯一判断依据,不是“看起来要满了”就扩。
什么时候真正发生扩容?看 len == cap 这一瞬
很多人误以为“追加就扩容”,结果在循环里写 s = append(s, x) 却没预估容量,导致底层数组反复拷贝。关键点在于:只有当前长度等于容量时,runtime 才决定换底层数组。
-
make([]int, 0, 2)创建后len=0, cap=2,前两次append都不扩容,直接写入原数组 - 第三次
append时len变成 3,而cap还是 2 → 触发扩容 - 空 slice(
cap == 0)首次append会直接分配 1 个元素空间,不是从 0 开始翻倍 - 调试时别只看
len,打印cap - len才是真实余量
append 扩容策略不是简单翻倍,而是分段 + 对齐
Go 的扩容逻辑藏在 src/runtime/slice.go,实际按当前 cap 分段处理,并强制 8 字节对齐——这不是拍脑袋定的,是为内存分配器效率服务。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
cap < 1024:新cap = old cap * 2(如 512 → 1024) -
cap >= 1024:新cap = old cap + old cap / 4,再向上取整到 8 的倍数(如 2048 → 2560,3000 → 3760) - 如果一次性追加大量元素(比如
cap=100但要塞 1000 个),runtime 会跳过倍增,直接按需分配 ≥ 期望值的最小合法容量 - 所有新容量都会向上对齐到 8 字节边界,这是为了减少内存碎片和提升分配速度
并发写 slice 必须自己加锁,append 不是原子操作
append 永远返回新结构体,它不就地修改原 slice。多个 goroutine 共享一个 slice 并调用 append,即使 cap 足够,也可能因竞态导致数据覆盖或 panic——因为赋值操作非原子,且底层写入位置可能重叠。
立即学习“go语言免费学习笔记(深入)”;
- 函数内要追加,必须显式返回新 slice:
func add(s []int, x int) []int { return append(s, x) } - 复用缓冲区(如网络包解析)时,反复用
buf[off:off+n],前一次残留数据可能被后续读到;安全方式是newS := append([]byte(nil), oldS...) - 从已有切片(如
data[100:200])再追加,注意它的cap可能只有 100,不够就会脱离原数组,失去共享优势
最常被忽略的一点:扩容后指针地址一定变,但是否“影响原 slice”取决于是否扩容——没扩就仍共享底层数组,扩了就完全独立。想确认是否扩容,最直接的方式是打印 &s[0] 地址对比,而不是猜或依赖文档描述。

















