Go 的 slice 底层数据结构是由一个 array 指针指向底层数组,len 表示切片长度,cap 表示切片容量。slice 的主要实现是扩容。对于 append 向 slice 添加元素时,假如 slice 容量够用,则追加新元素进去,slice.len++,返回原来的 slice。当原容量不够,则 slice 先扩容,扩容之后 slice 得到新的 slice,将元素追加进新的 slice,slice.len++,返回新的 slice。
//slice 在函数内没有出现扩容,函数外和函数内 slice 变量指向是同一个数组,
//则函数内复制的 slice 变量值出现更改,函数外这个 slice 变量值也会被修改
func change(bb []int) {
for k, _ := range bb {
bb[k] = k + 1
}
}
//slice 在函数内出现扩容,则函数内变量的值会新生成一个数组
//也就是新的 slice,而函数外的 slice 指向的还是原来的 slice,则函数内的修改不会影响函数外的 slice。
func add(bb []int) {
bb = append(bb, 1)
}
func main(tt *testing.T) {
bb := make([]int, 4)
for i := 0; i < 4; i++ {
bb[i] = i
}
fmt.Println(bb) //[0 1 2 3]
change(bb)
fmt.Println(bb) //[1 2 3 4]
add(bb)
fmt.Println(bb) //[1 2 3 4]
}
对于切片的扩容规则:当切片比较小时(容量小于 1024),则采用较大的扩容倍速进行扩容(新的扩容会是原来的 2 倍),避免频繁扩容,从而减少内存分配的次数和数据拷贝的代价。当切片较大的时(原来的 slice 的容量大于或者等于 1024),采用较小的扩容倍速(新的扩容将扩大大于或者等于原来 1.25 倍),主要避免空间浪费,网上其实很多总结的是 1.25 倍,那是在不考虑内存对齐的情况下,实际上还要考虑内存对齐,扩容是大于或者等于 1.25 倍。