切片扩容机制
🟡 中等题目描述
解释 Go 语言中切片的扩容机制。以下代码的输出是什么?
func main() {
s := make([]int, 0, 3)
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
s = append(s, 1)
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
s = append(s, 2)
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
s = append(s, 3)
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
s = append(s, 4)
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}期望输出
len=0 cap=3 []
len=1 cap=3 [1]
len=2 cap=3 [2]
len=3 cap=3 [3]
len=4 cap=6 [1 2 3 4]提示
- 切片容量不足时会触发扩容
- 扩容策略与 Go 版本有关
- Go 1.18+ 前后有差异
解法
参考答案 (3 个标签)
切片 扩容机制 内存分配
核心概念
切片由三部分组成:
type slice struct {
ptr unsafe.Pointer // 指向底层数组的指针
len int // 长度
cap int // 容量
}Go 1.18+ 扩容策略
// 简化版扩容逻辑
func growslice(oldCap, newLen int) int {
newcap := oldCap
doublecap := newcap + newcap
if newLen > doublecap {
newcap = newLen
} else {
const threshold = 256
if oldCap < threshold {
// 小切片:容量翻倍
newcap = doublecap
} else {
// 大切片:增长 25%
for 0 < newcap && newcap < newLen {
newcap += (newcap + 3*threshold) / 4
}
}
}
// 内存对齐(考虑到元素大小)
// ... 省略对齐逻辑
return newcap
}扩容规则总结
| 旧容量 | 预期容量 | 新容量(Go 1.18+) | 增长比例 |
|---|---|---|---|
| 0 | 1 | 取决于类型 | - |
| 1 | 2 | 2 | 100% |
| 2 | 3 | 4 | 100% |
| 3 | 4 | 6 | 100% |
| 4 | 5 | 8 | 100% |
| … | … | … | 100% |
| 256 | 257 | 512 | 100% |
| 512 | 513 | 848 | ~65.6% |
| 1024 | 1025 | 1408 | ~37.5% |
关键点
- 小切片(< 256):容量翻倍
- 大切片(≥ 256):增长约 25%
- 内存对齐:实际容量可能会因为内存对齐而更大
- 新数组:扩容会创建新数组,复制原数据
代码验证
func main() {
s := make([]int, 0, 3)
// cap = 3,未扩容
s = append(s, 1, 2, 3)
fmt.Printf("len=%d cap=%d\n", len(s), cap(s)) // len=3 cap=3
// cap = 3 → 6,触发扩容
s = append(s, 4)
fmt.Printf("len=%d cap=%d\n", len(s), cap(s)) // len=4 cap=6
// cap = 6,未扩容
s = append(s, 5, 6)
fmt.Printf("len=%d cap=%d\n", len(s), cap(s)) // len=6 cap=6
// cap = 6 → 12,触发扩容
s = append(s, 7)
fmt.Printf("len=%d cap=%d\n", len(s), cap(s)) // len=7 cap=12
}扩展:切片共享底层数组的问题
func main() {
s1 := make([]int, 3, 5) // [0, 0, 0], cap=5
s1[0], s1[1], s1[2] = 1, 2, 3
// s2 共享 s1 的底层数组
s2 := s1[1:3] // [2, 3], cap=4
fmt.Println(s1, s2) // [1 2 3] [2 3]
// 修改 s2 会影响 s1
s2[0] = 99
fmt.Println(s1, s2) // [1 99 3] [99 3]
// 追加 s2 可能覆盖 s1 的元素
s2 = append(s2, 4, 5)
fmt.Println(s1, s2) // [1 99 3] [99 3 4 5]
// 再追加会触发扩容,创建新数组
s2 = append(s2, 6)
fmt.Println(s1, s2) // [1 99 3] [99 3 4 5 6]
// 此时 s2 和 s1 不再共享底层数组
}