一、误区
最近面试很多候选人,在问到slice的扩容机制时,大多回答的是:当容量小于1024时,双倍扩容,大于1024时,1.25倍扩容。这种回答虽不能说完全错,但是不够准确和完整。究其原因,往往是看了网上给的一些常见回答,并没有进一步的深入理解。
这篇文章希望通过阅读源码,梳理一下slic扩容的完整流程,并说明在golang版本1.18之后所做的一些调整。
二、示例
func Test_GrowSlice(t *testing.T) {
// int64的切片示例
var s1 = make([]int64, 0)
for i := 0; i < 1025; i++ {
s1 = append(s1, 1)
}
fmt.Printf("slice1 len=%d, cap=%d \n", len(s1), cap(s1))
//输出:slice1 len=1025, cap=1280
// int32的切片示例
var s2 = make([]int32, 0)
for i := 0; i < 1025; i++ {
s2 = append(s2, 1)
}
fmt.Printf("slice2 len=%d, cap=%d \n", len(s2), cap(s2))
// 输出:slice2 len=1025, cap=1344
}
看到第一个输出应该不会觉得奇怪,与我们理解的一致,1024 * 1.25 = 1280,容量扩容为1280。
但是第二个输出就有点超出预期了,逻辑一样的代码,仅仅是把切片的元素类型修改为int32,容量怎么就变成1344了呢。接下来我们分析完源码,就可以解释了。
三、源码分析
首先,我们先看下golang库下runtime/slice.go文件,其中的growslice函数,就是我们要分析的主要逻辑。ssl
func growslice(et *_type, old slice, cap int) slice {
// 省略.....
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.cap < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
// 省略......
}
先看下growslice函数中,最开始的这段代码。 这段逻辑就是我们通常理解的:当容量小于1024时,双倍扩容,大于1024时,1.25倍扩容。好多人看到这里就浅尝辄止了,想要完整理解,我们还要接着往下看。