
《Go语言精进之路》切片相关章节学习笔记及实验。
说切片之前,先看看Go语言数组。Go数组是一个固定长度的、容纳同构类型元素的连续序列,因此Go数组类型具有两个属性:长度及类型:
var a [1]int
var b [2]byte
var c [3]string
数组的特性是声明即定长,无法原地进行扩容,所以Golang中一般来说需要使用到数组的场合都会使用切片来替代。
切片的特性:
切片的本质是引用底层数组头指针+当前切片长度+底层数组大小:即array、len和cap:
type slice struct {array unsafe.Pointer // 底层数组头指针len int // 当前切片长度 cap int // 底层数组最大容量
}
使用时通过make关键字来进行初始化切片分配一个新的底层数组,或者是基于指定的数组进行切片化:
s := make([]byte,5)

u := [10]byte{11,12,13,14,15,16,17,18,19,20} // 长度为10的数组
s := u[3:7] // 从数组的第3个元素至第6个元素建立切片(前闭后开区间)


切片能够在底层数组容量不够时进行扩容,每次append时如果容量不足,会创建2倍大小于老数组的新数组,并复制老数组数据至新的数组中:
func main() {var s []int // 初始化时,底层数组为数组为nils = append(s, 11) fmt.Println(len(s), cap(s)) // 1 1s = append(s, 12) fmt.Println(len(s), cap(s)) // 2 2s = append(s, 13)fmt.Println(len(s), cap(s)) // 3 4s = append(s, 14)fmt.Println(len(s), cap(s)) // 4 4s = append(s, 15)fmt.Println(len(s), cap(s)) // 5 8
}

func BenchmarkAppendZeroSlice(b *testing.B) {maxNum := 10000for i := 0; i < b.N; i++ {// 空切片var zeroSlice []intfor j := 0; j < maxNum; j++ {zeroSlice = append(zeroSlice, j)}}
}func BenchmarkAppendCapSlice(b *testing.B) {maxNum := 10000for i := 0; i < b.N; i++ {// 声明cap的切片capSlice := make([]int, 0, maxNum)for j := 0; j < maxNum; j++ {capSlice = append(capSlice, j)}}
}

从基准测试结果可以看出,声明了cap的切片能避免频繁创建底层数组及复制,单轮append时间为18847ns,相较于零值切片的82081ns提升了4.3倍性能。