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。