go基础第五篇:slice扩容
1、
func main() { var s = make([]int, 5, 8) for i := 0; i < len(s); i++ { s[i] = i + 1 } fmt.Println(s, len(s), cap(s)) var s2 = s var s3 = s[:] var s4 = s[:3] var s5 = s[4:] var s6 = s[2:4] fmt.Println(s2, len(s2), cap(s2)) fmt.Println(s3, len(s3), cap(s3)) fmt.Println(s4, len(s4), cap(s4)) fmt.Println(s5, len(s5), cap(s5)) fmt.Println(s6, len(s6), cap(s6)) s[0] = 255 fmt.Println(s, len(s), cap(s)) fmt.Println(s2, len(s2), cap(s2)) fmt.Println(s3, len(s3), cap(s3)) fmt.Println(s4, len(s4), cap(s4)) fmt.Println(s5, len(s5), cap(s5)) fmt.Println(s6, len(s6), cap(s6)) }
把s赋值给s2后,s2和s是两个不同的切片(可以通过%p打印出变量地址),但共享底层数组,长度和容量均相同。切片的结构(见内置的SliceHeader)是一个三元组(指向底层数组的指针、长度、容量),赋值时,这三项会被逐一复制。
通过截取获得的slice,与原slice共享底层数组。故s3、s4、s5、s6与s共享底层数组。
cap(slice)返回的是从slice起始位置到底层数组末尾的元素数量,即底层数组长度-切片起始索引。s2的起始位置是底层数组的索引0,
2、
func main() { x := []int{1, 2, 3, 4} y := x[:2] fmt.Println(x, cap(x)) fmt.Println(y, cap(y)) y = append(y, 30) fmt.Println(x, cap(x)) fmt.Println(y, cap(y)) y = append(y, 31) fmt.Println(x, cap(x)) fmt.Println(y, cap(y)) y = append(y, 32) fmt.Println(x, cap(x)) fmt.Println(y, cap(y)) y = append(x[1:3], 33) fmt.Println(x, cap(x)) fmt.Println(y, cap(y)) y = append(x[1:3], 34, 35) fmt.Println(x, cap(x)) fmt.Println(y, cap(y)) }
如果append操作的slice的底层数组中在slice之后有元素,则append将覆盖slice之后的元素,append一个元素就覆盖一个元素,append两个元素就覆盖两个元素。append操作slice,如果要扩容,则只会把slice中的元素复制到新数组中,slice之前的元素不会复制。
最后y的容量为什么是6???是因为x[1:3]的容量是3吗?
3、
func main() { x := []int{1, 2, 3, 4} y := x[2:] fmt.Println(cap(x), cap(y)) y = append(y, 30) fmt.Println("x:", x) fmt.Println("y:", y) }
4、
func main() { var s1 = []int{1, 2, 3, 4, 5} fmt.Println(s1, len(s1), cap(s1)) var s2 = append(s1, 6) fmt.Println(s1, len(s1), cap(s1)) fmt.Println(s2, len(s2), cap(s2)) var s3 = append(s2, 7) fmt.Println(s2, len(s2), cap(s2)) fmt.Println(s3, len(s3), cap(s3)) }
s2的容量为什么是10?扩容后容量是怎么变化的?
扩容机制见runtime包中slice.go文件(即slice struct所在文件)的growslice函数,主要分为两步:
第一步:调用nextslicecap函数,返回值是新数组的容量。nextslicecap函数的逻辑是:如果旧容量<256,则返回max(2*旧容量, slice新长度)。否则,按照按照0.25*旧容量+3/4*256的逻辑递增,直到大于slice新长度。newcap += (newcap + 3*threshold) >> 2。
第二步:内存对齐。在第一步的基础上,根据数组中元素所占字节数的不同,执行不同的逻辑,得到最终的新容量。
5、
func main() { s1 := make([]int, 6) s2 := make([]int, 1024) s1 = append(s1, 1) s2 = append(s2, 2) fmt.Println(len(s1), cap(s1)) fmt.Println(len(s2), cap(s2)) }
6、
func main() { var s1 []int var s2 = []int{} var s3 = make([]int, 0) var s4 = *new([]int) fmt.Println(s1, s2, s3, s4) fmt.Println(s1 == nil, s2 == nil, s3 == nil, s4 == nil) fmt.Println(reflect.DeepEqual(s1, s4), reflect.DeepEqual(s2, s3)) fmt.Println(len(s1), len(s2), len(s3), len(s4)) fmt.Println(cap(s1), cap(s2), cap(s3), cap(s4)) }
比较两个slice是否相同,可以直接调用reflect.DeepEqual(s1, s2)进行比较。如果一个是nil分片(如var s1 int[]),一个是空分片(如s2 :=[]int{}),会返回false。
浙公网安备 33010602011771号