切片容量的追加
在使用make的时候不仅可以制定切片的长度,还可以制定切片的容量。
例如:
var numbers = make([]int,3 ,5)
fmt.Printf("长度 = %d, 容量= %d, slice = %v\n", len(numbers), cap(numbers), numbers) // cap()函数为取出切片的容量
用下图来展示一下make分配的内存空间内部的结构(这里的内存空间在每次扩容时会重新分配,不会是原本的内存地址):
- 使用完make之后,numbers是指向切片的头部的一个首指针
- ptr为指向切片尾部的指针,指向切片合法元素的最后一个位置
- ptr之后的是容量预留的空间长度(预留的为5个长度,但是已经使用了3个)
使用append向数组中追加元素:
var numbers = make([]int,3 ,5)
fmt.Printf("长度 = %d, 容量= %d, slice = %v\n", len(numbers), cap(numbers), numbers) // cap()函数为取出切片的容量
//向numbers切片追加一个元素1,numbers的长度变为4,numbers 的值为[0,0,0,1],但是容量依旧为5
numbers = append(numbers, 1)
fmt.Printf("长度 = %d, 容量= %d, slice = %v\n", len(numbers), cap(numbers), numbers)
运行结果:
如果已经使用的长度已经到达容量预留的最大值,再使用append追加时,内存空间不够,golang会重新分配内存空间,地址发生变化,容量进行扩展,并不是在原有基础上扩展(下面为求证实验)。
golang的append扩展内存机制研究
扩展的内存大小研究
验证方式:每次扩容时比较容量和长度的差
使用以下代码来检测每次自动扩展时扩展的内存空间的大小:
a := make([]int, 0)
n := 65536
nowCap := 0
for i := 0; i < n; i++ {
a = append(a, 1)
if nowCap != cap(a) {
nowCap = cap(a)
fmt.Printf("len=%d cap=%d 扩大了:%d\n", len(a), cap(a), cap(a)-len(a)+1)
}
}
运行结果:
len=1 cap=1 扩大了:1
len=2 cap=2 扩大了:1
len=3 cap=4 扩大了:2
len=5 cap=8 扩大了:4
len=9 cap=16 扩大了:8
len=17 cap=32 扩大了:16
len=33 cap=64 扩大了:32
len=65 cap=128 扩大了:64
len=129 cap=256 扩大了:128
len=257 cap=512 扩大了:256
len=513 cap=1024 扩大了:512
len=1025 cap=1280 扩大了:256
len=1281 cap=1696 扩大了:416
len=1697 cap=2304 扩大了:608
len=2305 cap=3072 扩大了:768
len=3073 cap=4096 扩大了:1024
len=4097 cap=5120 扩大了:1024
len=5121 cap=7168 扩大了:2048
len=7169 cap=9216 扩大了:2048
len=9217 cap=12288 扩大了:3072
len=12289 cap=15360 扩大了:3072
len=15361 cap=19456 扩大了:4096
len=19457 cap=24576 扩大了:5120
len=24577 cap=30720 扩大了:6144
len=30721 cap=38912 扩大了:8192
len=38913 cap=49152 扩大了:10240
len=49153 cap=61440 扩大了:12288
len=61441 cap=76800 扩大了:15360
Process finished with exit code 0
可以看到扩展时的规则为:当容量(cap)小于1000时,扩展时扩展为之前的两倍,当容量大于1000时,扩展时扩展为之前的1.25倍。
验证扩展时是否完全重新分配内存空间
研究append时,内存扩展时是不是在原有基础上扩展,还是完全重新开辟了新的地址。
验证方式:每次扩容时打印内存地址。
代码如下:
a := make([]int, 1)
fmt.Printf("a切片的内存地址 = %p\n", a)
fmt.Printf("a[0]内存地址 = %p\n", &a[0])
a = append(a, 1) // 向a切片追加一个元素为1
fmt.Println("==========自动扩展内存=================")
fmt.Printf("a切片的内存地址 = %p\n", a)
fmt.Printf("a[0]内存地址 = %p\n", &a[0])
fmt.Printf("a[1]内存地址 = %p\n", &a[1])
运行结果:
可以发现扩容前和扩容后内存地址不一样,所以是重新分配内存地址。
切片的长度len和容量cap
切片的截取
截取和python的数组切片基本一致,按照左闭右开来取出切片。
部分截取
s := []int{1,2,3}
s1:= s[0:2] // 按照左取右不取的原则取出第0和第1个元素即[1,2]
fmt.Println(s1)
运行结果:
符合左闭右开的截取规则
注意:s和s1的数组指向的地址是相同的,所以修改s1中的元素,s切片中的值也会响应的修改.
例如:
s := []int{1,2,3}
s1:= s[0:2] // 按照左取右不取的原则取出第0和第1个元素即[1,2]
fmt.Println(s1)
s1[0] = 100
fmt.Println(s)
fmt.Println(s1)
运行结果:
结论就是,当把截取之后的切片直接赋值给新的一个切片,新的切片指向的地址和被截取的切片的地址是相同的,也就是浅拷贝。
数组的深拷贝
使用copy(新切片, 被拷贝的切片)
来完成深拷贝,这样个切片的空间是独立分开的。
例如:
s := []int{1,2,3}
s2 := make([]int, 3) // s2 = [0,0,0]
copy(s2,s) // 将s的值深拷贝到s2中
fmt.Printf("s2 = %d\n", s2)
fmt.Printf("s的内存地址 = %p\n", &s)
fmt.Printf("s2的内存地址 = %p\n", &s2)
运行结果:
可以看到深拷贝的内存地址是不同的。
此处评论已关闭