众所周知,golang中切片在容量不足时会自动扩容,那么扩容后的切片容量到底是多少呢?
浏览了很多文章,绝大多数给出的答案都是小于1024时为原来的2倍,大于1024时是1.25倍,因为源码中这样一段代码
// src/runtime/slice.go:100
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
for newcap < cap {
newcap += newcap / 4
}
}
}
看上去的确是这样,但是,newcaps的计算并没有结束,所以,上面的说法严谨的来说是错误的。
newcaps在进行了翻倍或者是增加25%后,还会进行接下来的计算
// src/runtime/slice.go:82
func growslice(et *_type, old slice, cap int) slice {
// ......
switch et.size {
case 1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
newcap = int(capmem)
case ptrSize:
lenmem = uintptr(old.len) * ptrSize
newlenmem = uintptr(cap) * ptrSize
capmem = roundupsize(uintptr(newcap) * ptrSize)
newcap = int(capmem / ptrSize)
default:
lenmem = uintptr(old.len) * et.size
newlenmem = uintptr(cap) * et.size
capmem = roundupsize(uintptr(newcap) * et.size)
newcap = int(capmem / et.size)
}
// ......
}
et代表slice中的元素类型,old是扩容前的切片,cap是扩容后的至少需要的最小容量,即old.caps + 本次新增的元素数量,ptrSize是指一个指针的大小,在64位中为8。到这里后会根据et.size的大小进入roundupsize去调整newcaps的大小。当入参小于32768时,主要依赖class_to_size,size_to_class8或size_to_class128这三个数组中的两个去调整newcaps。
// src/runtime/msize.go:35
func roundupsize(size uintptr) uintptr {
// _MaxSmallSize = 32768
if size < _MaxSmallSize {
// smallSizeMax = 1024
if size <= smallSizeMax-8 {
return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])
} else {
return uintptr(class_to_size[size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]])
}
}
// _PageSize = 1 << 13
if size+_PageSize < size {
return size
}
return round(size, _PageSize)
}