Golang中切片容量为什么比预期的大
Golang中切片容量为什么比预期的大 对于下面提到的代码:
package main
import ( "fmt")
func main() {
a := []int{12} // … 让编译器确定长度
fmt.Println(len(a))
fmt.Println(cap(a))
a=append(a,112)
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
a=append(a,234)
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))}
输出: 1 1 [12 112] 2 2 [12 112 234] 3 4
请问谁能解释一下为什么输出的最后一行是4而不是3?
更多关于Golang中切片容量为什么比预期的大的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中切片容量为什么比预期的大的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
这种神奇的效果来自于 append 函数。当 append 函数发现当前切片的容量不足以容纳更多元素时,它会创建一个容量翻倍的新切片。
package main
import "fmt"
func main() {
var s []int
printSlice(s)
// append works on nil slices.
s = append(s, 0)
printSlice(s)
// The slice grows as needed.
s = append(s, 1)
printSlice(s)
// We can add more than one element at a time.
s = append(s, 2)
printSlice(s)
// We can add more than one element at a time.
s = append(s, 3)
printSlice(s)
// We can add more than one element at a time.
s = append(s, 5, 6, 7, 8, 9)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
输出:
len=0 cap=0 [] len=1 cap=1 [0] len=2 cap=2 [0 1] len=3 cap=4 [0 1 2] len=4 cap=4 [0 1 2 3] len=9 cap=10 [0 1 2 3 5 6 7 8 9]
如果容量是成倍增长的,为什么最后容量是10,而不是16?有什么想法吗?
这是因为Go语言切片在容量不足时进行扩容的机制导致的。当切片容量不足以容纳新元素时,append()函数会创建一个新的底层数组,容量会按照特定算法增长,而不是简单地增加1。
在Go 1.18+版本中,切片的扩容规则大致如下:
- 当原切片容量小于256时,新容量会翻倍(2倍)
- 当原切片容量大于等于256时,新容量会按照公式
newcap = oldcap + (oldcap + 3*256)/4增长
在你的代码中:
package main
import "fmt"
func main() {
a := []int{12}
fmt.Printf("初始状态: len=%d, cap=%d\n", len(a), cap(a)) // len=1, cap=1
a = append(a, 112)
fmt.Printf("第一次append后: len=%d, cap=%d\n", len(a), cap(a)) // len=2, cap=2
a = append(a, 234)
fmt.Printf("第二次append后: len=%d, cap=%d\n", len(a), cap(a)) // len=3, cap=4
}
具体过程:
- 初始切片
a长度为1,容量为1 - 第一次
append(a, 112)时,容量1不够,需要扩容。由于原容量1<256,新容量翻倍为2 - 第二次
append(a, 234)时,容量2不够,再次扩容。原容量2<256,新容量翻倍为4
所以最终长度为3,容量为4。这是为了减少频繁的内存分配,提高性能。你可以通过查看 runtime 包的源码来了解具体的扩容逻辑:
// runtime/slice.go 中的扩容函数(简化示意)
func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice {
// ...
newcap := oldCap
doublecap := newcap + newcap
if newLen > doublecap {
newcap = newLen
} else {
const threshold = 256
if oldCap < threshold {
newcap = doublecap
} else {
// 大于等于256时的增长策略
for 0 < newcap && newcap < newLen {
newcap += (newcap + 3*threshold) / 4
}
}
}
// ...
}
这种设计在需要多次追加元素时能显著提升性能,因为减少了内存分配的次数。

