深入理解Golang Slice
数据结构
type slice struct {
array unsafe.Pointer
len int
cap int
}
- slice的底层数据结构中的array是一个指针,指向的是一个Array
- len代表这个slice的元素个数
- cap表示slice指向的底层数组容量
对slice的赋值,以值作为函数参数时,只拷贝1个指针和2个int值。
创建
- var []T 或 []T{}
- func make([]T,len,cap) []T
nil切片和空切片
- nil 切片被用在很多标准库和内置函数中,描述一个不存在的切片的时候,就需要用到 nil 切片。比如函数在发生异常的时候,返回的切片就是 nil 切片。nil 切片的指针指向 nil.
- 空切片一般会用来表示一个空集合。比如数据库查询,一条结果也没有查到,那么就可以返回一个空切片。
扩容 - 计算策略
- 若 Slice cap 大于 doublecap,则扩容后容量大小为 新 Slice 的容量(超了基准值,我就只给你需要的容量大小)
- 若 Slice len 小于 1024 个,在扩容时,增长因子为 1(也就是 3 个变 6 个)
- 若 Slice len 大于 1024 个,在扩容时,增长因子为 0.25(原本容量的四分之一)
扩容 - 内存策略
- 翻新扩展:当前元素为 kindNoPointers,也就是非指针类型,将在老 Slice cap 的地址后继续申请空间用于扩容
- 举家搬迁:重新申请一块内存地址,整体迁移并扩容
拷贝
slicecopy 方法会把源切片值(即 from Slice )中的元素复制到目标切片(即 to Slice )中,并返回被复制的元素个数,copy 的两个类型必须一致。slicecopy 方法最终的复制结果取决于较短的那个切片,当较短的切片复制完成,整个复制过程就全部完成了。
特性
slice的array存储在连续内存上,因此具有以下特点:
1. 随机访问很快,适合下标访问,缓存命中率很高;
2. 动态扩容会涉及内存拷贝和开辟新内存,会带来gc压力,内存碎片化;
3. 如果可预估使用空间,提前分配cap的大小是极好的;
4. 新、老slice公用底层数组,对底层数组的更改都会影响到彼此;
5. append可以掰断新老slice共用底层数组的关系;
参考资料
[1] 深入解析Go中Slice底层实现 https://halfrost.com/go_slice/
[2] 深入解析Go Slice https://segmentfault.com/a/1190000017341615