GoLang的切片底层及扩展规则

1.结构

在第一部分中,元素存储在哪里?第二部分,存储多少元素,可以存储多少元素

2.变量声明 int 类型切片

比如声明一个[] int,变量ints由右边的三部分组成。 slice 的元素应该存储在一个连续的内存中,实际上是一个数组。 data是底层数组的起始地址,但是只分配了slice结构,右边还没有分配底层数组,所以datau003dnil,存储个数为0,容量为0

3.make定义int类型切片

3.len 等于 0

var sNil []int //底层数组指向nil

var sEmpty u003d make([]int, 0) //底层数组指向一个内存地址,但是没有分配空间

fmt.Println(len(sNil), len(sEmpty), cap(sNil), cap(sEmpty))//输出:0

3.2len不等于0

如果这个变量以make的方式定义,它不仅会分配这三部分结构体,还会开辟一段内存作为其底层数组。 make会为int开辟一段包含五个整形元素的内存,并将它们初始化为整形默认值0。不过目前这个切片变量只存储了两个元素,所以data指向图中的位置,len是 2,上限是 5

功能主() {

var ints []int u003d make([]int, 2, 5)

fmt.Println(ints)//输出:[0]

}

接下来,让我们尝试添加一个要追加的元素。由于已经保存了两个元素,新的就是第三个,len修改为3

功能主() {

var ints []int u003d make([]int, 2, 5)

fmt.Println(len(ints), cap(ints)) //输出:2 5

fmt.Println(ints)//输出:[0]

整数 u003d 追加(整数,1)

fmt.Println(len(ints), cap(ints))//输出:3 5

fmt.Println(ints)//输出:[0 1]

}

存储的元素可以安全读写,但是超出这个范围就是跨界访问,会出现panic

4.new 定义字符串类型切片

ner,一个sice变量,也会分配这三部分结构体,但是它不负责底层数组的分配,所以datau003dnil,存储个数为0,容量为0。返回值of new 是 slice 结构的起始地址,所以 ps 是一个地址。此时切片变量没有底层数组,所以 (*) [0]u003d"eggo" 是不允许的

那么是谁给他分配了底层数组呢?使用append通过append添加元素,他会为slice打开底层数组。在这里,需要容纳一个字符串元素数组。字符串类型由两部分组成,一是内容的起始地址(值是到字符串的内容),二是字节长度

5.底层切片数组

数组是同种数据的元素一个一个的存储;

int slice,最底层是int数组;字符串类型的底层是字符串数组;

但是,对于 slice,数据不必指向数组的开头

对于切片数据,不必指向数组的开头。示例如下:变量arr是一个容量为10的整型数组,声明后数组容量不变。我们可以将不同的切片关联到同一个数组,即var s 1 [int] u003darr[1:4],它们会共享底部数组;

s1的元素是1、2、3,这三个元素加到f1中,但是容量从data开始到底部数组结束,一共9个元素; s2 的元素是从索引 7 到末尾的三个元素; slice 访问和修改底层数组的元素

对于s1,如果访问s1[3],即arr[4],则访问越界

功能主() {

arr :u003d []int{2, 3, 5, 7, 11, 13}

fmt.Println(arr)

sli1 :u003d arr[1:4]

fmt.Println(sli1) //[3 5 7]

fmt.Println(len(sli1)) //3

fmt.Println(cap(sli1)) //5

sli2 :u003d arr[0:4]

fmt.Println(sli2) //[2 3 5 7]

fmt.Println(len(sli2)) //4

fmt.Println(cap(sli2)) //6

sli3 :u003d arr[2:3]

fmt.Println(sli3) //[5]

fmt.Println(len(sli3)) //1

fmt.Println(cap(sli3)) //4

捆[0] u003d 100

fmt.Println(sli3)//[100]

fmt.Println(arr)//[2 3 100 7 11 13]

}

可以通过append修改范围或添加元素,扩大可读可写范围

这时候如果给s2添加元素,原来的底层数组就不能再使用了。您必须打开一个新数组。复制原始编号并添加新元素。将元素个数改为4个,将容量扩大到6个

功能主() {

arr :u003d []int{2, 3, 5, 7, 11, 13}

slie :u003d arr[4:]

谎言[0] u003d 100

fmt.Println(arr) //输出:[2 3 5 7 100 13]

slie u003d 追加(slie,200)

slie[0] u003d 1000

fmt.Println(arr)//输出:[2 3 5 7 100 13]

}

6.slice扩展规则

oldcap:扩容前的容量

上限:所需的最小容量

newcap:估计容量;

  1. 计算预计扩容:如果扩容前翻倍的产能仍小于最小容量(5),则预计容量等于所需的最小容量。否则需要细分;如果展开前的元素个数小于1024,则直接翻倍。如果扩容前的元素个数大于等于1024,则扩容四分之一,也就是原来的1.25倍

  1. 估计容量是估计元素的“数量”。这么多元素需要多少内存?这与元素类型相关联。将估计的容量乘以元素类型大小以获得所需的内存。当然,直接分配这么多内存是不行的。简而言之,就是因为申请内存分配不是直接和操作系统协商的,而是和语言本身的内存管理模块协商的,内存管理模块会提前向操作系统申请一批内存并进行管理它按照通用规格。当我们申请内存时,它将帮助我们匹配足够大和最接近的规格。这就是我们在第三步中需要做的,也就是估计应用的内存与合适的内存规格的匹配度;

1、上例中,当预估容量为5位和64位时,需要申请40字节来存储扩展的底层数组,但实际申请会匹配48字节。这么大的内存能容纳多少元素?本例中,每个元素占用8个字节,一共可以安装6个。这是扩容后的容量

7.字符串切片扩展示例

A是string类型的slice,每个元素在64位下占16个字节;

1、第一步:扩容前,容量为3,加一个元素,扩容到至少4,原来容量的两倍,即3*2大于4,3小于1024。它直接。预计容量 newcap 为 6;

2.第二步,将估计容量乘以元素大小,即6 * 16 u003d 96字节

  1. 第三步:匹配内存规格为96字节,所以最终扩容后的容量为6

8.slice源码

运行时Go下的切片可以看到

类型切片结构 {

数组 unsafe.Pointer

长度整数

上限整数

}

/*

初始化函数makeslice:

math.MulUintptr:根据元素大小和容量上限计算需要的内存空间

mallocgc:对于内存分配,使用 32K 作为临界值。小的分配在P的缓存中,大的分配在堆堆中

*/

func makelice(et *_type, len, cap int) unsafe.Pointer {}

/*

长度小于1024时,上限翻倍;当大于 1024 时,增加 1 / 4。但这不是绝对的。我们会尽量根据元素的类型进行优化

*/

func growslice(et *_type, old slice, cap int) slice {}

/*

复制 slicecopy:

核心函数是memmove,from u003d > 移动size的数据,size为元素大小*from和to中较小长度的个数

*/

func slicecopy(toPtr unsafe.Pointer, toLen int, fromPtr unsafe.Pointer, fromLen int, width uintptr) int {}

9.扩容和地址重新分配

var s u003d make([]int, 2, 2)

s[0] u003d 1

fmt.Println(unsafe.Pointer(&s[0])) //0xc0000aa090

s[1] u003d 2

fmt.Println(unsafe.Pointer(&s[0])) //0xc0000aa090

// 提示:扩容后slice数组的地址会被重新分配

s u003d 追加(s, 3)

fmt.Println(unsafe.Pointer(&s[0])) //0xc0000a80a0

var s u003d make([]int, 2, 2)

s[0] u003d 1

s[1] u003d 2

fmt.Println(unsafe.Pointer(&s)) //0xc000004078

fmt.Println(unsafe.Pointer(&s[0])) //0xc0000160d0

10.截取切片但指向相同的底层数组

var s u003d make([]int, 2, 2)

s[0] u003d 1

s[1] u003d 2

fmt.Println(unsafe.Pointer(&s)) //0xc000004078

fmt.Println(unsafe.Pointer(&s[0])) //0xc0000160d0

a :u003d s[:2]

fmt.Println(unsafe.Pointer(&a)) //0xc000004090

fmt.Println(unsafe.Pointer(&a[0])) //0xc0000160b0

11.复制切片

var s u003d make([]int, 2, 2)

b :u003d make([]int, 2)

// 提示:如果要复制切片,请使用复制方法

copy(b, s) //copy会重新分配底层数组的内存

fmt.Println(unsafe.Pointer(&s[0])) //0xc0000160b0

fmt.Println(unsafe.Pointer(&b[0])) //0xc0000160c0