1 问题所在
对于切片扩容机制,我最初的理解就是以下:
如果切片的容量小于1024,则扩为原来容量的两倍,如果切片容量超过1024,则扩为原来的1.25倍。
于是,我写出以下代码准备验证一下这件事:
func main() {
a := make([]int, 3) //a为3
print("a before append:")
slicePrint(a)
a = append(a, 1)
print("a after append:")
slicePrint(a)
b := make([]int, 100) //b为100
print("b before append:")
slicePrint(b)
b = append(b, 1)
print("b after append:")
slicePrint(b)
c := make([]int, 1000) //c为1000
print("c before append:")
slicePrint(c)
c = append(c, 1)
print("c after append:")
slicePrint(c)
}
func slicePrint(s []int) {
fmt.Printf("len=%d cap=%d \n", len(s), cap(s))
}
输出的结果为:
a before append:len=3 cap=3
a after append:len=4 cap=6
b before append:len=100 cap=100
b after append:len=101 cap=224
c before append:len=1000 cap=1000
c after append:len=1001 cap=1536
可以看到的是,a切片的cap确实是变成了2倍,而b、c切片并没有按照期望那样去扩充为2倍或者是1.25倍,故可以推断出在go 1.20版本下,开头那段盛行的话是错误的。于是,我们从源码去看看到底是怎么扩容的。
2 源码分析
根据网上的信息,append()函数在扩容时调用的是…/src/runtime/slice.go文件中的growslice()函数,那么我们来看一下这个函数。这个函数很长,我们分为几个部分来看。
2.1 注释部分
在这里,我截取了注释中的参数和返回值部分,做一个简单说明。
// growslice allocates new backing store for a slice.
//
// arguments:
//
// oldPtr = pointer to the slice's backing array
// newLen = new length (= oldLen + num)
// oldCap = original slice's capacity.
// num = number of elements being added
// et = element type
//
// return values:
//
// newPtr = pointer to the new backing store
// newLen = same value as the argument
// newCap = capacity of the new backing store
可以看到的是,函数的参数有5个,分别是指针类型的oldPtr,指向原切片的数据地址;int类型的newLen,oldCap以及num,分别代表着新的切片长度,原切片的容量以及加入切片的元素个数;最后是一个类型的指针et,这个是切片中元素的数据类型,例如int类型与int32类型会有区别。函数的返回值有3个,分别是newPtr即新的地址,newLen新的长度,newCap新的容量。
另外,也可以从剩下的注释中得到一些其他的信息,所以源码中的注释有必要看一下。
2.2growSlice()函数主体部分
以下是go 1.20版本中的完整的growSlice()函数
func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice {
oldLen := newLen - num
if raceenabled {
callerpc := getcallerpc()
racereadrangepc(oldPtr, uintptr(oldLen*int(et.size)), callerpc, abi.FuncPCABIInternal(growslice))
}
if msanenabled {
msanread(oldPtr, uintptr(oldLen*int(et.size)))
}
if asanenabled {
asanread(oldPtr, uintptr(oldLen*int(et.size)))
}
if newLen