深入理解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