切片
基本的结构
type struct {
Data uintptr
Len int
Cap int
}
一个切片是由数据指针加上长度和容量组成的,类似与C++中的vector。
常见的初始化操作
var (
a []int // 等价于nil
b = []int{} // 表示一个空的切片,不等于nil
c = []int{1,2,3} // len,cap都为3的切片
d = c[:2] // cap为3,len为2。与c共享一片内存地址
e = c[0:2:cap(c)] // cap为3,len为2。与c共享一片内存地址
f = c[:0] // cap为3,len为0。
g = make([]int,3) // cap为3,len为3。里面的参数都是nil
h = make([]int,2,3) // cap为3,len为2。里面参数前两个是nil
)
对于切片的操作
添加
一般使用append方法对于切片进行元素添加。
a = append(a,0)
a = append(0,a)
第二行是在开头插入元素,一般会导致内存的重新分配,从而性能比较差。
可以利用copy方法和append方法进行组合,实现一些功能。
a = append(a,0)
copy(a[i+1:0],a[i:]) // a[i:]向后移动一个位置
a[i] = x
copy可以避免多个切片拷贝的时候共享一片内存时进行修改导致的bug。
扩容
在对于切片进行添加的时候,会自动触发切片的扩容的操作。
对于具体的扩容策略,最基本的是当cap小于1024时,每次翻倍。大于1024时每次变为原来的1.25倍。
但是这个策略只适用于每次增加一个元素的情况,如果一次增加多个元素的时候,就不一定是这个扩容策略了。
如果当前cap为4,len为3,一次append6个元素。那么这个时候,append操作之后的cap就是9。
并且具体的slice的类型对于cap也有关系。
package main
import "fmt"
func main() {
a := []byte{1, 0}
a = append(a, 1, 1, 1)
fmt.Println("cap of a is ", cap(a))
b := []int{23, 51}
b = append(b, 4, 5, 6)
fmt.Println("cap of b is ", cap(b))
c := []int32{1, 23}
c = append(c, 2, 5, 6)
fmt.Println("cap of c is ", cap(c))
type D struct {
age byte
name string
}
d := []D{
{1, "123"},
{2, "234"},
}
d = append(d, D{4, "456"}, D{5, "567"}, D{6, "678"})
fmt.Println("cap of d is ", cap(d))
}
cap of a is 8
cap of b is 6
cap of c is 8
cap of d is 5
这里面的基本原理涉及到go的内存管理操作,不是很懂。
https://blog.csdn.net/weixin_34100227/article/details/91373911
删除
a = []int{1,2,3}
a = a[:len(a) - N] // 删除尾部N个元素
a = a[N:] // 删除头部N个元素
a = a[:i+copy(a[i:],a[i+N:])] // 删除中间的N个元素
使用技巧
可能的内存泄漏
因为Go的GC机制,只要存在内存引用,整片内存都不会被释放掉。可能会降低整体的系统性能。最好是只把感兴趣的数据单独寸一个切片。