Go中的切片
1,切片的介绍和结构
1,切片的结构
切片是作为动态数组的简化版。所以切片的长度可以动态的变化。在Go语言的实践中,因为数组因为不灵活的原因很少使用,因此掌握切片是非常重要的。首先要了解切片的底层结构。
//这是go语言底层结构
type SliceHeader struct {
Data uintptr //指针指向底层维护的数组
Len int //Len代表切片可使用的容量
Cap int //Cap代表切片指向底层数组最大的容量
}
因此对于Slice的结构图
//创建一个切片
var s []int=make([]int,2,3)
其结构图为
由于只初始化了切片,因此底层数组默认的值为0。在这个初始化中,len=2,cap=3。因此切片可访问的范围为0~1,如果访问第三个元素的时候,会产生越界的问题。如果想动态扩充数组可以使用append()函数,可以扩充切片的容量。但是当扩充后的Cap>oldCap,又得创建新的数组(这块在第二节会详细讲解)。
2,切片的初始化
切片初始化的方法有许多形式,但是掌握了切片的结构并不难。
//直接创建切片
var s []int=[]int{1,2,3} //此时cap==len
//类型推断
s:=[]int{1,2,3}
//通过make创建切片
s:=make([]int,2,3) //指定切片的类型,切片的len,切片的cap,cap>=len
//通过new创建
s:=new([]int) //这种创建方式返回的是指针类型,并且没有给定底层数组,不能直接使用切片。
//通过引用其他数组或者是切片
var arr [3]int=[3]int{1,2,3}
s:=arr[1:2] //此时切片的cap=2,len=1。cap取决于引用数组的位置
2,切片的扩容问题
1,切片的扩容
切片的扩容问题,当切片通过append()函数对切片进行扩容时。可能会产生数组的拷贝,但是根据不同情形扩容的规则也是不一样的。
直接上案例
s:=[]int{1,2} //此时cap=len=2,
s=append(s,3,4,5) // 此时切片Cap=5
这时我们应该考虑扩充后的cap是否等于5?
在切片动态扩充有这样一个规则。
(1) 当oldCap * 2<Cap时,NewCap=Cap.
(2) 否则有以下两种情形。
(3)oldLen<1024 NewCap=oldCap * 2 (这里的1024指的是切片中元素的数目)
(4)oldLen>1024 NewCap=oldCap * 1.25
此时2*2<5,因此扩充的NewCap为5 ,创建一个新的数组Cap为5,将原来数组的内容拷贝过去 。但是我们只探讨了我们能看到的部分,在操作系统这部分分配了多少内存呢?
2,切片扩容后需要占用多大的内存
此部分的内容参考了B站幼麟实验室up主的视频
从上面那个例子,我们发现扩充后的Cap为5,但是在内存中真的只分配5*int(所占字节大小)的内存吗?答案肯定不是的.这是因为在编程语言之中申请内存并不是直接与操作系统直接干涉,而是和编程语言自身实现的内存管理模块。内存管理模块预先会向操作系统申请一批内存,分成不同的规格管理起来。我们申请内存时会匹配到足够大,并且最接近的规格。
在64位计算机下,申请5个int型所需的字节位 5 * 8=40字节。此时匹配到48个字节的内存块。这就是go语言中扩容后内存的分配策略
3,切片相关的函数及其注意事项
1,切片的相关的函数
(1)append(),动态的添加元素
s:=[]int{1,2,3}
s=append(s,4,5,6) //append()返回的是一个切片,后面的参数可以为多个
(2)copy(slice1,slice2),切片的拷贝操作,将s2的内容拷贝到s1
s:=[]int{1,2,3}
s2:=make([]int,10)
copy(s2,s)
fmt.Println("s=",s) //input [1,2,3]
fmt.Println("s2=",s2) //input [1,2,3,0,0,0,0,0,0,0]
//我们此时修改s2中的值,看是否会影响到s
s2[0]=9
fmt.Println("s=",s) //input [1,2,3]
fmt.Println("s2=",s2) //input [9,2,3,0,0,0,0,0,0,0]
//从这里我们可以发现,这是一个值拷贝。
//当cap(s2)<cap(s)时,此时只会将s的前cap(s2)数量的元素拷贝到s2
(3)通过append()函数的使用,实现对元素的删除
s:=[]int{1,2,3}
//比如我们删除2
s=append(s[:1],s[2:]...)
fmt.Print("s=",s) //Input =[1,3]
2,切片的注意事项
(1)在切片的使用当中要尽量降低底层数组的扩容,从第二节我们就知道,切片的扩容可能会导致内存的重新分配,这样大大降低了程序的运行效率。
(2)在切片的使用中也要尽量避免内存泄露,避免一个小的内存引用造成整个内存没有被垃圾回收。