而若容量不够,会创建一个新的容量足够的底层数组,先将之前数组的元素复制过来,再将新元素追加到后面,然后返回新的 slice,底层数组改变
而这里对新数组的容量定义是按 乘以2 的机制增加相关教程推荐:《golang教程》
而今天看到关于 golang 切片的底层结构即 reflect.sliceheader 时,发现 append 的扩容并不完全是2倍增长,源码如下(go version 1.13):
// grow grows the slice s so that it can hold extra more values, allocating// more capacity if needed. it also returns the old and new slice lengths.func grow(s value, extra int) (value, int, int) { i0 := s.len() i1 := i0 + extra if i1 < i0 { panic(reflect.append: slice overflow) } m := s.cap() if i1 <= m { return s.slice(0, i1), i0, i1 } if m == 0 { m = extra } else { for m < i1 { if i0 < 1024 { m += m } else { m += m / 4 } } } t := makeslice(s.type(), i1, m) copy(t, s) return t, i0, i1}// append appends the values x to a slice s and returns the resulting slice.// as in go, each x's value must be assignable to the slice's element type.func append(s value, x ...value) value { s.mustbe(slice) s, i0, i1 := grow(s, len(x)) for i, j := i0, 0; i < i1; i, j = i+1, j+1 { s.index(i).set(x[j]) } return s}
首先 append 判断类型是否 slice,然后调用 grow 扩容,从 l1 <= m 的判断可以发现确实容量足够的情况下,只是对原始数组建立一个新的 slice
但当容量不足时,可以看到只有在当前元素 i0 小于1024时,才是按2倍速度正常,否则其实每次只增长25%,代码验证如下:
func main() { str := make([]int, 1023) fmt.println(len(str), cap(str)) str = append(str, 1) fmt.println(len(str), cap(str)) str = append(str, 1) fmt.println(len(str), cap(str))}输出:1023 10231024 20481025 2048
初始容量已经达到1024后,只增长了256
func main() { str := make([]int, 1024) fmt.println(len(str), cap(str)) str = append(str, 1) fmt.println(len(str), cap(str)) str = append(str, 1) fmt.println(len(str), cap(str))}输出:1024 10241025 12801026 1280
当然这里还有个疑惑在于,初始容量为1023时,并不是按1023×2,而是直接1024×2,测试初始500扩容也是直接按512×2,猜测更底层在动态调整时总会补充为2的n次方,目前builtin包下只看到 append 的定义说明,还有待继续挖掘
以上就是关于golang slice的append扩容的详细内容。
