<p>cap() 返回底层数组的总容量,而非当前已用元素数;实际可用余量为 cap(s) - len(s),切片扩容仅在 len 等于 cap 时触发。</p>

cap() 返回什么,为什么它不是“当前用了多少”
cap() 返回的是底层数组还能塞多少元素,不是已存元素个数——那个是 len()。很多人一看到 cap(s) == 100 就以为“还能加 100 个”,结果发现 len(s) 已经是 95,实际只剩 5 个空位。真正可用余量是 cap(s) - len(s),调试时直接打印这个差值比单看 cap() 更靠谱。
从已有数组切出来的新 slice,cap 可能远大于 len,比如 data := make([]int, 1000); s := data[100:200],此时 len(s) == 100,但 cap(s) == 900(因为底层数组还剩 800 个后续位置 + 当前起始偏移)。这种 slice 追加时若不超过 900,就不会扩容;一旦超过,就脱离原数组,拷贝到新内存。
make([]T, 0, N) 是最便宜的 cap 预设方式
别用 var s []int 或 s := []int{} 开头,它们的 cap 是 0,第一次 append 就得分配 1 个元素空间,后面立刻面临 1→2→4→8… 的微扩容链。直接写 s := make([]int, 0, 1000),len 是 0,cap 是 1000,前 1000 次 append 全部零拷贝。
- 读取文件行数已知?
make([]string, 0, lineCount) - HTTP 查询参数最多 20 个?
make([]Param, 0, 20) - 数据库批量查出约 500 条?
make([]*User, 0, 512)(宁可稍大,别卡在 500 刚好满) - 不确定精确值?按历史 P95 长度 ×1.2,或起步设
cap=16—— 小 slice 频繁翻倍(1→2→4→8→16)的开销比多占几字节更伤
扩容触发条件只有 len == cap 这一个瞬间
只要 len(s) ,无论 <code>cap 是 10 还是 10000,append 都只是往原底层数组末尾写,不分配、不拷贝。扩容不是“append 多了就扩”,而是“刚填满最后一格时,下一次 append 才扩”。所以别靠经验猜“差不多该满了”,运行时检查 cap(s) - len(s) 才真实。
立即学习“go语言免费学习笔记(深入)”;
扩容策略分段生效:
-
cap < 1024:新cap = old cap * 2(512 → 1024) -
cap >= 1024:新cap = old cap + old cap / 4,再向上对齐到 8 的倍数(2048 → 2560,3000 → 3760) - 一次性
append(s, a, b, c...)时,runtime 会先算总需容量,再按上述规则扩到 ≥ 所需的最小合法值
并发写 slice 必须自己管,cap 再大也救不了竞态
append 永远返回新 slice 结构体,不是就地修改。多个 goroutine 同时对同一个变量 s 执行 s = append(s, x),必然发生数据覆盖或 panic——因为赋值非原子,且底层写入位置可能重叠。即使 cap 足够大,也解决不了这个问题。
安全做法只有两种:
- 加锁(
sync.Mutex或sync.RWMutex)保护整个 append + 赋值过程 - 改用 channel 或其他线程安全结构传递数据,让单个 goroutine 统一收集
另外,复用 slice(如 s = s[:0])只清 len,不改变 cap,适合循环中反复使用;但要注意旧数据残留问题,必要时用 copy 或显式置零。


















