Go切片扩容是动态策略:仅当len>cap时触发,新容量依当前cap和需求而定,≤256时翻倍,≥256时按约25%递增,大量追加则直接分配合适容量,扩容即换底层数组并复制。
Go 切片扩容不是“固定倍数”的机械操作,而是一套兼顾性能与内存效率的动态策略。理解它,关键不在死记倍数,而在抓住两个核心:什么时候扩、扩多少。
只有 append 时长度超出当前容量(len > cap)才会真正扩容。只要还有空余容量,append 就只是改长度、填数据,不换底层数
组。
s := make([]int, 2, 4),再 append(s, 1, 2) —— 长度从 2 变成 4,但容量仍是 4,不扩容append(s, 5) —— 长度要变成 5,超了 cap=4,这时才触发扩容Go 1.18+ 的实际策略是:
注意:这里说的“256”是 Go 运行时内部阈值(非文档公开常量),不是硬编码的 1024——旧资料中提到的 1024 是 Go 1.18 之前的逻辑,已淘汰。
扩容后,切片指针指向一块全新分配的内存,原数组内容被完整拷贝过去。这意味着:
unsafe.Pointer(&s[0]) 打印地址验证是否扩容make([]T, 0, N) 预留足够 cap 能显著提升性能切片之间是否共享内存,只取决于它们是否指向同一底层数组——跟是否扩容无关。比如:
s1 := arr[1:4] 和 s2 := s1[1:] 天然共享,改 s1[1] 会影响 s2[0]s2 := append(s1, x) 是否影响 s1,取决于这次 append 是否扩容:没扩 → 共享;扩了 → 独立dst := make([]T, len(src)); copy(dst, src)
基本上就这些。不复杂但容易忽略——重点始终是:看 cap、看 append 是否溢出、看地址是否变。