slice内存模型
我的go源码版本是:go1.17.2
Go_SDK\go1.17.2\src\runtime\slice.go
首先我们来看一下slice的结构:
type slice struct {
array unsafe.Pointer
len int
cap int
}
slice的结构非常简单,只有三个部分:
- array: 指向数组的指针。
- len:当前长度。
- cap:容量。
slice的初始化
举一个例子:
声明一个整型的slice:
var ints []int
intsslicedata = nillen = 0cap = 0
通过make来定义
var ints []int = make([]int, 2, 5)
data指向这个底层数组的首地址 | len = 2 | cap = 5
这个时候我们添加一个元素,然后再做一个赋值。
ints = append(ints, 1)
ints[0] = 1
panic
再来举一个例子:
slicenew
ps := new([]string)
data = nil | len = 0 | cap = 0
(*ps)[0] = "egg" //不允许
此时这个slice变量还没有底层数组。像上述的操作是不允许的。
但是我们可以使用append的方式为slice添加元素。它就会为slice开辟底层数组。
slice的底层数组
int型slice的底层就是int型数组,string型slice的底层就是string型数组。
但是slice中的数组指针,并不是必须指向底层数组的开头。
我们来看一个例子:
arr := [10]{0,1,2,3,4,5,6,7,8,9} //数组容量声明了就不可改变
我们可以把不同的slice关联到同一个数组。
像这样:
var s1 []int = arr[1:4] //左闭右开
var s2 []int = arr[7:]
s1s2
slice的扩容规则
cap
STEP1 预估扩容后的容量 newCap
预估规则:
- 如果扩容前容量翻倍,还是小于所需的最小容量,那么预估容量就等于所需的最小容量。
- 否则就要再细分:
- 如果扩容前元素个数小于1024,那就直接翻倍。
- 如果扩容前元素大于等于1024,那就先扩容至原来的 1/4 。
STEP2 newCap需要多大内存
预估容量 * 元素类型大小
但是不可以直接分配这么多内存。
简单来说,再许多编程语言中,申请内存并不是直接与操作系统交涉的,而是与语言自身实现的内存管理模块进行交涉。它会提前向操作系统申请一块内存,分成常用的规格管理起来,我们申请内存时,它会帮我们匹配到足够大且最接近的规格。(按照第一步的扩容规则来)
这就是第三步要做的事情。
STEP3 匹配到合适的内存规格
5 * 8 = 40
按照扩容规则,实际申请时会匹配到48字节。一共能装6个元素。
我们来看一个例子:
创建一个string类型的slice:
a := []string{"my", "name", "is"}
a := append(a, "egg")
step1: newCap = 6
step2: 6 * 16 = 96 byte (64位中string类型的大小是16byte)
step3: 匹配到内存规格就是96字节。
所以最终扩容以后,cap就是6。