切片(slice)是Golang中数组之上的抽象;可按需自动增长与缩小,其底层是连续的内存块,切片是包含三个成员变量数据结构,如下所示:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
先看下切片定义和初始化的例子:
var (
a []int // nil切片,和nil相等,一般用来表示一个不存在的切片
b = []int{} // 空切片,和nil不相等,一般用来表示一个空的集合
c = []int{1, 2, 3} // 有3个元素的切片,len=3,cap=3
d = c[:2] // 有2个元素的切片,len=2,cap=3
e = c[0:2:cap(c)] // 有2个元素的切片,len=2,cap=3
f = c[:0] // 有0个元素的切片,len=0,cap=3
g = make([]int, 3) // 有3个元素的切片,len=3,cap=3
h = make([]int, 2, 3) // 有2个元素的切片,len=2,cap=3
i = make([]int, 0, 3) // 有0个元素的切片,len=0,cap=3
)
j := c[1:2] //j包含1个元素2,len=2-1,cap=3,其内存还是使用c切片的内存,并未copy
以建立byte的切片为例,介绍slice创建和初始化过程
var a []byte 定义一个byte的sice,但是未分配任何内存,此时不能直接使用,必须配合make函数进行初始化后才能使用:
var a []byte //不做任何初始化就会创建一个nil切片
a = make([]byte,0) //空切片,长度和容量都是0,但是不为nil,可以append操作
a = make([]byte,0,100)//切片长度未0,容量未100,空切片
a = make([]byte,3,100) //切片长度为3,初始值为{0,0,0},容量为100
切片长度是切片中实际存入的数据长度,容量是切片初始分配的内存空间,Slice依托数组实现,追加数据通过append(返回值一定要再赋值给原slice),容量不足时会自动扩容,如果容量小于1024,自动扩容为原来2倍,如果容量超过1024,自动扩容为1.25倍,这里一定要注意,如果使用slice进行数据处理时,尽量初始化时分配足够的空间,不要触发切片自动扩容,自动扩容不仅会占用更多的空间,也会存在数据移动操作,消耗资源。
初始化时,也可通过如下几种方式:
a := make([]byte,0,100)
var a []byte = []byte{1,2,3}
a := []int{10: 5} //:=表示变量a类型通过后面表达式推理得到,a第十个元素为5,其他为0,len=cap=11
通过也有切片创建切片,也就是创建切片的子切片,其共享底层内存数组,内存空间使用的就是原始数组的空间,因此在不扩容时修改切片的内容,会影响到其他共享内存的切片:
dst = src[low : high : max]
三个参分别表示:
新切片原始切片的low下标元素为起始,len = high - low, cap = max - low, high 和 max不能超出㡳切片,否则会段错误
如果没有指定max,则max的值为原始切片容量-low。
a := []int{10: 99} //len=cap=11
b := a[1:3] //元素值为{0,0} len=3-1=2,cap=11-1=10
c := a[:5] //low=0,相当于a[0:5],len=5,cap=11
d := a[8:] //high=11,len=3,cap=11-8=3
e := a[1:5:5] //len=4 cap=5
f := a[:] //相当于指针赋值,表示同一个切片
主要介绍切片的访问,遍历,插入,删除等操作
可通过下表直接方位,访问不能超过其长度,否则出错:
a := []string{"aaa","bbb","ccc"} //声明并初始化为string的切片,len=cap=3
b := a[3] //b为string类型值为"ccc"
fmt.Println(a[1]) //打印为字符串"bbb"
切片也是一个集合,通过range遍历元素
a := []string{"aaa", "bbb", "ccc"}
for k, v := range a {
fmt.Println(k, " ", v) //k为下表,v为值
}
/*其运行结果为:
0 aaa
1 bbb
2 ccc
*/
注意:
a[k] = "xxx"
通过函数append可在切片尾部追加元素,如下:
a := []byte{1,2,3}
a = append(a,1) //在a尾部增加一个byte元素1
a = append(a,[]byte{3,4}...)//在a尾部增加一个元素为3,4的byte切片,切片组合,把[]byte{3.4}切片中元素的值逐个增加到a的尾部
b := []byte{7,8}
a = apend(a,b...)//在a的尾部插入b切片中的元素
注意此种追加方式,如果cap重组,则不会重新分配内存,如果cap不足会触发cap扩容
如果要在切片头部增加元素,则必然会引起内存重新分配与赋值操作,插入方式如下:
a := []byte{1,2,3}
a = append([]byte{5}, a...) //头部必须是与a相同类型的slice,就相当于先把a追加到匿名变量[]byte{1}的尾部,
//再把此匿名变量的指针赋值给a,所以a已经不是原来分配的内存空间了
a = append([]byte{1,2,3}, a...)
所以尽量避免在slice头部插入元素,特别是长度加大的slice
还有一种情况,可能要在slice非头部和尾部的地方插入元素,此时会设计数据移动,也可能会使得slice扩容,其方式可通过以下代码实现:
a := []byte{1,2,3}
alen = len(a)
b := []byte{4,5} //插入到a的第一个元素和第二个元素之间
a = append(a, b...) // 先把长度增加到len(a)+len(b)
copy(a[1+len(b):], a[1:alen]) // 后移len(b)个元素
copy(a[1:1+len(b)], b)
删除切片指定元素,Go 标准库并未给出相应的函数,需要我们自己实现,删除的元素若是指针,可能还会被底层数组引用中,从而无法被gc回收,为了能及时的释放,在删除前,先把对应元素设置为nil再删除;
在切片头尾删除时,通过子切片生成的方式最简单,如下方式:
a[len(a)-1]=nil
a = a[:len(a)-1]
a[1] = nil
a = a[1:]
若要删除中间的元素,可通过append移动的方式:
// append追加,覆盖被删除元素
a = append(a[:i], a[i+N:]...)
// 复制,覆盖被删除元素
a = a[:i+copy(a[i:], a[i+N:])]
也可以通过遍历的方式来实现,这个可自行实现即可
在流媒体中会需要对码流数据进行解复用/复用/抽帧/组帧/解码等操作,通过切片的方式能够更加灵活的进行byte数据进行处理,经常会用到切片的插入/拷贝/移动截取等操作,为了更高的数据处理性能,slice使用时需要注意以下方面:
rtpbuf := make([]byte, 0, 1480)
payload := rtpbuf[headlen:]
rtppkt.Timestamp = binary.BigEndian.Uint32(rtpbuf[4:8])
binary.BigEndian.PutUint32(rtpheader[8:12], p.SSRC)
buf := make([]byte, 0, 1400+14)
buf = append(buf, []byte{13:0})
data := buf[14:]
header := buf[:14]
...//码流封装和分片
data = append(data,payload...)
//填充头部,例如头部为12个RTPheader
header := buf[2:14]
//rtp头部封装,直接使用header切片生成
...
sendbuf := buf[2:]
//发送数据
...