文章目录
GoLang之切片底层系列三(深度学习)
注: 本文代码以Go SDK v1.18测试
1.slice类型基本结构
slice由三个部分组成:
data:元素存哪里
len:存了多少个元素
cap:可以存多少个元素
2.var创建
举个例子,声明一个整型slice:
var ints []int
变量ints的结构如下图所示:
slice的元素要存在一段连续的内存中,实际上就是个数组 ,data就是这个底层数组的起始地址。但目前只分配了这个切片结构,还没有分配底层数组,所以data为nil,存储元素个数len为0,容量cap也为0。
3.make创建
如果通过make的方式定义这个变量,不仅会分配这三部分结构,还会开辟一段内存作为它的底层数组。
func main() {
var ints []int = make([]int, 2, 5)
fmt.Println(ints)//输出:[0 0]
}
这里make会为ints开辟一段容纳5个整型元素的内存,还会把它们初始化为整型的默认值 0。但是目前这个slice变量只存储了两个元素,所以data指向图中位置,len为2,cap为5,所以变量ints的结构如下图所示。
接下来,添个元素试试~
func main() {
var ints []int = make([]int, 2, 5)
fmt.Println(len(ints), cap(ints)) //输出 :2 5
fmt.Println(ints)//输出:[0 0]
ints = append(ints, 1)
fmt.Println(len(ints), cap(ints))//输出: 3 5
fmt.Println(ints)//输出: [0 0 1]
}
已经存了两个,所以新添加的是第三个,len修改为3。
ints[0] = 1
已经存储的元素是可以安全读写的,但是超出这个范围就属于越界访问,会发生panic。
4.new创建
再来个例子,这次我们看看字符串类型的slice,但是不用make,来试试new。
ps := new([]string)
func main() {
ps := new([]string)
if ps == nil {
fmt.Println("yes")
} else {
fmt.Println("no") //no
}
}
new一个slice变量同样会分配这三部分结构,但它不负责底层数组的分配,所以data=nil,len和cap都是0。new的返回值就是slice结构的起始地址,所以ps它就是个地址。
此时这个slice变量还没有底层数组,像下面这样的操作是不允许的:
(*ps)[0] = "eggo"
那谁来给它分配底层数组呢?
答案是:append
*ps = append(*ps, "eggo")
通过append的方式添加元素,append就会给它开辟底层数组。如下图所示,这里开辟了一个字符串元素的数组。
注意其中字符串类型由两部分组成,一个内容起始地址,指向字符串内容,还有一个字节长度。
接下来我们看看和slice密切相关的——底层数组。
5.底层数组
注意其中字符串类型由两部分组成,一个内容起始地址,指向字符串内容,还有一个字节长度。
接下来我们看看和slice密切相关的——底层数组。
arr := [10]int{0,1,2,3,4,5,6,7,8,9}
变量arr是容量为10的整型数组(注意数组容量声明了就不能变了)。我们可以把不同的slice关联到同一个数组,如下代码所示:
var s1 []int = arr[1:4]
var s2 []int = arr[7:]
s1和s2会共用底层数组arr,s1和s2的具体结构如下图所示:
s1的元素是arr索引1到4 左闭右开,所以1,2,3这3个元素算是添加到s1中了。但是容量却是从s1的data这里开始,到底层数组结束共有9个元素。
slice访问和修改的都是底层数组的元素,所以s1[3]就算访问越界了。
如果修改s1的定义为:
var s1 []int = arr[1:5]
或者通过append添加元素来扩大可读写的区间范围:
s1 = append(s1,4)
这样就可以访问s1[3]了。
再来看s2,s2的元素从索引7开始直到结束,共3个元素,容量也是3。此时
如果再给s2添加元素会怎样?
s2 = append(s2, 10)
arr这个底层数组是不能用了,得开辟新数组。但是原来的元素要拷过来,还要添加新元素10。元素个数改为4,容量扩到6。这下slice和底层数组的关系都清晰了吧!
不过,还有个问题,我只添加了一个元素,s2怎么从3扩容到6了呢?那就要看slice的扩容规则了~
6.扩容规则(1.15)
扩容规则第一步:预估扩容后的容量。
怎么预估?来看个例子。
ints := []int{1,2}
ints = append(ints, 3, 4, 5)
这里扩容前容量oldCap为2,添加三个元素,那至少得扩容到cap=5吧?难道就预估到5,这么简单粗暴的吗?
当然不是,预估也是有规则的~
oldCap:扩容前容量
oldLen:扩容前元素个数
cap:扩容所需最小容量
newCap:预估容量
Go1.15中,预估容量规则如下:
(1)如果扩容前的容量翻倍之后还是小于所需最小容量,那么预估容量就等于所需最小容量。
(2)如果不满足第一条,而且扩容前容量小于1024,那就直接翻倍没商量。
(3)如果不满足第一条,而且扩容前容量大于等于1024,那就循环扩容四分之一,直到大于等于所需最小容量。
在上面这个例子中,扩容前容量为2,就算翻倍了还是小于5,所以预估容量就是5。
扩容规则第二步:
预估容量只是预估的元素“个数”,这么多元素需要占用多少内存呢?这就和元素类型挂钩了。
用预估的容量,乘以元素类型大小,得到的就是所需内存。
难道直接分配这么多内存就ok了?并不是。
简单来说,是因为在许多编程语言中,申请分配内存并不是直接与操作系统交涉,而是和语言自身实现的内存管理模块。它会提前向操作系统申请一批内存
分成常用的规格管理起来,我们申请内存时,它会帮我们匹配到足够大、且最接近的规格。
这就是第三步要做的事情:将预估申请内存匹配到合适的内存规格。
在我们的例子中,预估容量为5,64位下就需要申请40字节存放扩容后的底层数组,而实际申请会匹配到48字节。
那这么大的内存能装多少个元素呢?
这个例子每个元素(int)占8字节,一共能装6个,这就是扩容后的容量了。
7.扩容规则(1.16)
Go1.16中有了些变化,和1024比较的不再是oldLen,而是oldCap。如下代码所示:
// 1.16
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.cap < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
8.扩容规则(1.18)
到了Go1.18时,又改成不和1024比较了,而是和256比较;并且扩容的增量也有所变化,不再是每次扩容1/4,如下代码所示:
//1.18
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
const threshold = 256
if old.cap < threshold {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
// Transition from growing 2x for small slices
// to growing 1.25x for large slices. This formula
// gives a smooth-ish transition between the two.
newcap += (newcap + 3*threshold) / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
趁热打铁,再来个例子练练手~
9.小练习
a是string类型的slice,64位下每个元素占16字节,以Go1.16为例。
a := []string{"My", "name", "is"}
a = append(a, "eggo")
第一步:
扩容前容量是3,添加一个元素,最少要扩容到4。
原容量翻倍为6,大于4;
且原容量和1024比,小于1024,所以直接翻倍,预估容量为6,预估容量newcap为6。
第二步:
预估容量乘以元素大小(6*16=96),等于96字节。
第三步:
96字节匹配到内存规格也是96字节。
所以,最终扩容后容量为6。
func main() {
a := []string{"My", "name", "is"}
fmt.Println(a, len(a), cap(a)) //[My name is] 3 3
a = append(a, "eggo")
fmt.Println(a, len(a), cap(a)) //[My name is eggo] 4 6
}
关于Go语言的slice就先介绍到这里~
10.内存分配规律
举个现实中的例子来说
你家里有五个人,每个人都想吃绿豆糕,因此你的需求就是 5,对应上例中的 cap ,于是你就到超市里去买。
但超市并不是你家开的,绿豆糕都是整盒整盒的卖,没有卖散的,每盒的数量是 6 个,因此你最少买 6 个。
每次购买的最少数量,就可以类比做 Go 的内存分配规律。
只有了解了 Go 的内存分配规律,我们才能准确的计算出我们最少得买多少的绿豆糕(得申请多少的内存,分配多少的容量)。
关于内存管理模块的代码,在 runtime/sizeclasses.go
从下面这个表格中,可以总结出一些规律:
在小于16字节时,每次以8个字节增加
当大于16小于2^8时,每次以16字节增加(2^8=256)
当大于2^8小于2^9时以32字节增加
依此规律…
// Code generated by mksizeclasses.go; DO NOT EDIT.
//go:generate go run mksizeclasses.go
package runtime
// class bytes/obj bytes/span objects tail waste max waste min align
// 1 8 8192 1024 0 87.50% 8
// 2 16 8192 512 0 43.75% 16
// 3 24 8192 341 8 29.24% 8
// 4 32 8192 256 0 21.88% 32
// 5 48 8192 170 32 31.52% 16
// 6 64 8192 128 0 23.44% 64
// 7 80 8192 102 32 19.07% 16
// 8 96 8192 85 32 15.95% 32
// 9 112 8192 73 16 13.56% 16
// 10 128 8192 64 0 11.72% 128
// 11 144 8192 56 128 11.82% 16
// 12 160 8192 51 32 9.73% 32
// 13 176 8192 46 96 9.59% 16
// 14 192 8192 42 128 9.25% 64
// 15 208 8192 39 80 8.12% 16
// 16 224 8192 36 128 8.15% 32
// 17 240 8192 34 32 6.62% 16
// 18 256 8192 32 0 5.86% 256
// 19 288 8192 28 128 12.16% 32
// 20 320 8192 25 192 11.80% 64
// 21 352 8192 23 96 9.88% 32
// 22 384 8192 21 128 9.51% 128
// 23 416 8192 19 288 10.71% 32
// 24 448 8192 18 128 8.37% 64
// 25 480 8192 17 32 6.82% 32
// 26 512 8192 16 0 6.05% 512
// 27 576 8192 14 128 12.33% 64
// 28 640 8192 12 512 15.48% 128
// 29 704 8192 11 448 13.93% 64
// 30 768 8192 10 512 13.94% 256
// 31 896 8192 9 128 15.52% 128
// 32 1024 8192 8 0 12.40% 1024
// 33 1152 8192 7 128 12.41% 128
// 34 1280 8192 6 512 15.55% 256
// 35 1408 16384 11 896 14.00% 128
// 36 1536 8192 5 512 14.00% 512
// 37 1792 16384 9 256 15.57% 256
// 38 2048 8192 4 0 12.45% 2048
// 39 2304 16384 7 256 12.46% 256
// 40 2688 8192 3 128 15.59% 128
// 41 3072 24576 8 0 12.47% 1024
// 42 3200 16384 5 384 6.22% 128
// 43 3456 24576 7 384 8.83% 128
// 44 4096 8192 2 0 15.60% 4096
// 45 4864 24576 5 256 16.65% 256
// 46 5376 16384 3 256 10.92% 256
// 47 6144 24576 4 0 12.48% 2048
// 48 6528 32768 5 128 6.23% 128
// 49 6784 40960 6 256 4.36% 128
// 50 6912 49152 7 768 3.37% 256
// 51 8192 8192 1 0 15.61% 8192
// 52 9472 57344 6 512 14.28% 256
// 53 9728 49152 5 512 3.64% 512
// 54 10240 40960 4 0 4.99% 2048
// 55 10880 32768 3 128 6.24% 128
// 56 12288 24576 2 0 11.45% 4096
// 57 13568 40960 3 256 9.99% 256
// 58 14336 57344 4 0 5.35% 2048
// 59 16384 16384 1 0 12.49% 8192
// 60 18432 73728 4 0 11.11% 2048
// 61 19072 57344 3 128 3.57% 128
// 62 20480 40960 2 0 6.87% 4096
// 63 21760 65536 3 256 6.25% 256
// 64 24576 24576 1 0 11.45% 8192
// 65 27264 81920 3 128 10.00% 128
// 66 28672 57344 2 0 4.91% 4096
// 67 32768 32768 1 0 12.50% 8192
// alignment bits min obj size
// 8 3 8
// 16 4 32
// 32 5 256
// 64 6 512
// 128 7 768
// 4096 12 28672
// 8192 13 32768
const (
_MaxSmallSize = 32768
smallSizeDiv = 8
smallSizeMax = 1024
largeSizeDiv = 128
_NumSizeClasses = 68
_PageShift = 13
)
var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 24, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536, 1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}
var class_to_allocnpages = [_NumSizeClasses]uint8{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 2, 3, 1, 3, 2, 3, 4, 5, 6, 1, 7, 6, 5, 4, 3, 5, 7, 2, 9, 7, 5, 8, 3, 10, 7, 4}
var class_to_divmagic = [_NumSizeClasses]uint32{0, ^uint32(0)/8 + 1, ^uint32(0)/16 + 1, ^uint32(0)/24 + 1, ^uint32(0)/32 + 1, ^uint32(0)/48 + 1, ^uint32(0)/64 + 1, ^uint32(0)/80 + 1, ^uint32(0)/96 + 1, ^uint32(0)/112 + 1, ^uint32(0)/128 + 1, ^uint32(0)/144 + 1, ^uint32(0)/160 + 1, ^uint32(0)/176 + 1, ^uint32(0)/192 + 1, ^uint32(0)/208 + 1, ^uint32(0)/224 + 1, ^uint32(0)/240 + 1, ^uint32(0)/256 + 1, ^uint32(0)/288 + 1, ^uint32(0)/320 + 1, ^uint32(0)/352 + 1, ^uint32(0)/384 + 1, ^uint32(0)/416 + 1, ^uint32(0)/448 + 1, ^uint32(0)/480 + 1, ^uint32(0)/512 + 1, ^uint32(0)/576 + 1, ^uint32(0)/640 + 1, ^uint32(0)/704 + 1, ^uint32(0)/768 + 1, ^uint32(0)/896 + 1, ^uint32(0)/1024 + 1, ^uint32(0)/1152 + 1, ^uint32(0)/1280 + 1, ^uint32(0)/1408 + 1, ^uint32(0)/1536 + 1, ^uint32(0)/1792 + 1, ^uint32(0)/2048 + 1, ^uint32(0)/2304 + 1, ^uint32(0)/2688 + 1, ^uint32(0)/3072 + 1, ^uint32(0)/3200 + 1, ^uint32(0)/3456 + 1, ^uint32(0)/4096 + 1, ^uint32(0)/4864 + 1, ^uint32(0)/5376 + 1, ^uint32(0)/6144 + 1, ^uint32(0)/6528 + 1, ^uint32(0)/6784 + 1, ^uint32(0)/6912 + 1, ^uint32(0)/8192 + 1, ^uint32(0)/9472 + 1, ^uint32(0)/9728 + 1, ^uint32(0)/10240 + 1, ^uint32(0)/10880 + 1, ^uint32(0)/12288 + 1, ^uint32(0)/13568 + 1, ^uint32(0)/14336 + 1, ^uint32(0)/16384 + 1, ^uint32(0)/18432 + 1, ^uint32(0)/19072 + 1, ^uint32(0)/20480 + 1, ^uint32(0)/21760 + 1, ^uint32(0)/24576 + 1, ^uint32(0)/27264 + 1, ^uint32(0)/28672 + 1, ^uint32(0)/32768 + 1}
var size_to_class8 = [smallSizeMax/smallSizeDiv + 1]uint8{0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}
var size_to_class128 = [(_MaxSmallSize-smallSizeMax)/largeSizeDiv + 1]uint8{32, 33, 34, 35, 36, 37, 37, 38, 38, 39, 39, 40, 40, 40, 41, 41, 41, 42, 43, 43, 44, 44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 48, 48, 48, 49, 49, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67}
11.底层数组(视频讲解)
数组,就是同种数据的元素一个挨着一个的存储;
int型slice,底层就是int数组;string型底层就是string数组;
但是对于slice的data并不是必须指向数组的开头
对于slice的data并不是必须指向数组的开头的例子如下:变量arr是容量为10的整形数组,数组容量声明后就不会再变了,我们可以把不同的slice关联到同一个数组即var s 1 [int] =arr[1:4],他们会共用底层数组;
s1的元素是1、2、3,这三个元素算是添加到f1中了,但是容量是从data开始到底层数组结束共有9个元素;s2的元素是从索引7开始直到结束,共3个元素;slice访问和修改的都是底层数组的元素
对于s1来说如果再访问s1[3]即arr[4]就算访问越界了
func main() {
arr := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var s1 []int = arr[1:4]
var s2 []int = arr[7:]
fmt.Println(s1, len(s1), cap(s1)) //[1 2 3] 3 9
fmt.Println(s2, len(s2), cap(s2)) //[7 8 9] 3 3
}
可以修改范围,或者通过append添加元素,来扩大可读写的区间范围
//append会把元素改变的,地址并不会改变
func main() {
arr := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Printf("%p\n", &arr) //0xc0000142d0
var s1 []int = arr[1:4]
var s2 []int = arr[7:]
fmt.Println(s1, len(s1), cap(s1)) //[1 2 3] 3 9
fmt.Println(s2, len(s2), cap(s2)) //[7 8 9] 3 3
s1 = append(s1, 100)
fmt.Printf("%p\n", &arr)//0xc0000142d0
fmt.Println(arr) //[0 1 2 3 100 5 6 7 8 9]
fmt.Println(s1, len(s1), cap(s1)) //[1 2 3 100] 4 9
fmt.Println(s2, len(s2), cap(s2)) //[7 8 9] 3 3
}
此时,如果再给s2添加元素的话,原来的那个底层数组是不能再用了,得开辟新数组,原来的数要拷过来,还要添加新元素,元素个数改为4,容量扩到6
func main() {
arr := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Printf("%p\n", &arr) //0xc00012e0f0
var s1 []int = arr[1:4]
var s2 []int = arr[7:]
fmt.Println(s1, len(s1), cap(s1)) //[1 2 3] 3 9
fmt.Println(s2, len(s2), cap(s2)) //[7 8 9] 3 3
s2 = append(s2, 100)
fmt.Printf("%p\n", &arr) // 0xc00012e0f0
fmt.Println(arr) //[0 1 2 3 4 5 6 7 8 9]
fmt.Println(s1, len(s1), cap(s1)) //[1 2 3] 3 9
fmt.Println(s2, len(s2), cap(s2)) //[7 8 9 100] 4 6
}
长度与容量如下变化如下演示:
func main() {
arr := []int{2, 3, 5, 7, 11, 13}
fmt.Println(arr)
sli1 := arr[1:4]
fmt.Println(sli1) //[3 5 7]
fmt.Println(len(sli1)) //3
fmt.Println(cap(sli1)) //5
sli2 := arr[0:4]
fmt.Println(sli2) //[2 3 5 7]
fmt.Println(len(sli2)) //4
fmt.Println(cap(sli2)) //6
sli3 := arr[2:3]
fmt.Println(sli3) //[5]
fmt.Println(len(sli3)) //1
fmt.Println(cap(sli3)) //4
sli3[0] = 100
fmt.Println(sli3)//[100]
fmt.Println(arr)//[2 3 100 7 11 13]
}
12.扩容规则(1.15)(视频讲解)
Go1.15中,预估容量规则如下:
oldcap:扩容前容量
cap:所需最小容量
newcap :预估容量 ;
oldLen:扩容前元素个数
1.计算预估扩容容量:
如果扩容前容量oldcap翻倍的话,还是小于所需最小容量cap,那么预估容量newcap就等于所需最小容量cap,否则,就要再细分;如果扩容前元素个数oldLen小于1024,那么预估容量newcap就等于扩容前容量oldcap两倍,如果扩容前元素个数oldLen大于等于1024,那么预估容量newcap就先在扩容前容量oldcap的基础上扩四分之一,也就是扩到原来的1.25倍
为何要要按以上的扩容规则呢?:
目的就是为了尽量可以扩容少的容量即可装下装入的元素,扩容前元素个数小于1024时,最少扩容oldcap的两倍
func main() {
a := []string{"My", "name", "is"}
fmt.Println(a, len(a), cap(a)) //[My name is] 3 3
fmt.Printf("%p\n", &a) //0xc000004078
fmt.Println(&a[0]) //0xc000110480
a = append(a, "eggo")
fmt.Println(a, len(a), cap(a)) //[My name is eggo] 4 6
fmt.Printf("%p\n", &a) //0xc000004078
fmt.Println(&a[0]) //0xc000068060 首地址已经变了
}
func main() {
ints := []int{1, 2}
fmt.Println(ints, len(ints), cap(ints)) //[1 2] 2 2
ints = append(ints, 3)
fmt.Println(ints, len(ints), cap(ints)) //[1 2 3 ] 3 4
}
2.预估容量newcap只是预估的元素的"个数",这么多元素,需要占多大内存呢?这就和元素类型挂钩了,用预估的容量乘以元素类型大小,得到的就是所需内存,当然并不是直接分配这么多内存就ok了,简单来说是因为申请分配内存并不是直接与操作系统交涉,而是和语言自身的内存管理模块交涉,内存管理模块会提前向操作系统申请一批内存,分成常用的规格管理起来,我们申请内存时,它会帮我们匹配到足够大且最接近的规格,这就是第三步需要做的事,即将预估申请内存匹配到合适的内存规格;
3.在上面的例子中,预估容量newcap为5,64位下就需要申请40字节存放扩容后的底层数组,而实际申请时会匹配到48字节,那这么大的内存能装多少个元素呢?在这个例子中每个元素占8字节,一共能装6个,这就是扩容后的容量