切片slice是golang中的一种非常重要和关键的数据类型,被大量地使用。本文总结数组arrays的使用,切片slice的使用以及它的底层是如何实现的。在了解底层之后,以后slice的使用上会更胸有成竹。
golang中的切片slice其实是数组arrays的一种抽象,所以要搞懂切片slice,就要先弄明白数组arrays。
数组arrays很好理解,就是一个固定长度、固定元素类型的数组。在go中数组类型包含两层意思:长度和元素类型。因此数组[2]int和数组[3]int,这两个是不同类型。虽然元素类型相同,但是长度不同。访问某个元素,可以使用下标的方式,array[n]会访问数组第n个元素,从0开始。
var a [2]int
var b [3]int
b[0] = 123
a = b
因为变量a和b是不同类型,所以上面的赋值语句在编译时就报错了:
./main.go:23:4: cannot use b (type [3]int) as type [2]int in assignment
Compilation finished with exit code 2
数组不需要主动的进行初始化,相对应的0值会在声明后被赋值。在内存中[2]int就是线性排列的2个int值,所以数组访问是O(1)的时间复杂度,速度极快。
var a [2]int // [0 0]
var c [3]string // [3个空字符串]
还有一点要搞清楚,数组arrays在go中是值,而不是指针。不像c或者java,数组是指向底层数组第1个元素的指针。因此在go中你赋值或者传递数组arrays,都会对整个数组内容进行一份复制。所以为了避免无谓的复制,我们会传递数组的指针,而不是数组。
初始化声明一般有两种。e这种声明不用填长度,编译器会帮你计算这个数。
d := [2]int{11, 22}
e := [...]int{11, 22}
fmt.Println(d == e) // 输出 true
由于数组arays的特性,在go代码中适用场景有限,而切片slices会用得非常多。切片slices基于数组,但提供了更高的灵活性。
[]T就是一个切片slices,和声明数组的区别就是没有指定长度。可以使用范围来截取切片,例如s1[x:y],会将s1中x位的元素至y-1位的元素截取。
d := [2]int{11, 22} // 数组类型
s1 := []int{11, 22, 33} // 切片类型
fmt.Println(s1[:2]) // [11 22]
fmt.Println(s1[1:2]) // [22]
创建一个切片会经常用到内建函数make,它的函数声明如下。我们可以看到,make可接受3个参数,第1个是切片,第2个是切片的长度,第3个是可选的容量大小。不指定cap容量的话,默认会和长度len相同。
func make([]T, len, cap) []T
s := make([]int, 5),s底层即为上图的数据结构。ptr是一个指针,指向底层对应的数组。len是切片的长度5,cap是底层数组的容量5。
当我们执行下面语句时 :
s2 := s[1:3]
做slicing的时候,go会新建一个slice值s2,而底层的数据是不动的。s2如上图深蓝色,通过更改指针、长度和容量来进行slicing。这也就是为什么slicing的性能非常高的原因。
一个slice不能越过cap进行操作,这个我们从底层很容易理解,因为就相当于越过底层数组的上界进行非法访问了。
一般我们会使用内部函数append来往切片slice后动态追加元素,当cap不够时,如果reslice后可以放下,那么它会reslice。例如上面的s2,底层的数组足以再追加2个元素。如果不行,那么它会new一个新的底层数组,大小为之前cap的两倍,并将之前的元素复制进去。下面测试了下:
s := make([]int, 5)
s = append(s, 6, 7)
fmt.Println(len(s), cap(s)) // 输出7 10
s = append(s, 8, 9, 10, 11)
fmt.Println(len(s), cap(s))//输出11 20
如果要追加一个slice到另一个slice的话,这样:
s5 := make([]int, 5)
s6 := []int{11, 22, 33, 44, 55, 66}
s := append(s5, s6...)
fmt.Println(s, len(s), cap(s))//输出[0 0 0 0 0 11 22 33 44 55 66] 11 12
在这样的场景下注意:如果我们只用到一个slice的一小部分,那么底层的整个数组也将继续保存在内存当中。当这个底层数组很大,或者这样的场景很多时,可能会造成内存急剧增加,造成崩溃。
所以在这样的场景下,我们可以将需要的分片复制到一个新的slice中去,减少内存的占用。例如一个很大的切片data里,我们需要的数据是data[m:n],那么我们创建一个新的slice变量r,将数据复制到r中返回。
mydata := data[m:n]
r := make([]int, len(mydata))
copy(r, mydata)
return r
通过了解数组array和切片slice的使用和底层原理,可以更透彻的理解他们的使用场景,里面有什么坑。这对我们平时编写程序是极有裨益的,后面我会继续深入,有时间再更。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。