首先,切片是什么?
切片slice是由三部分组成,类似于结构体
1.底层数组的地址(data)
2.存了多少个元素(len)
3.可以存多少个元素(cap)
举个例子:
var slice []int
slice元素要存在一段连续的内存中,实际上就是个数组。
data就是这个底层数组的起始地址,但目前只分配了切片结构,还没有分配底层数组,所以这里为nil,存储个数为0,容量也为0.
如果通过make的方式定义
var slice []int = make([]int, 2, 5)

开辟了一段容纳5个int元素的内存作为它的底层数组,初始化为0.
这样data就指向了指定数组的地址了,当前存了2个元素,容量为5.
现在追加一个元素看看
slice = append(slice, 1)

已经存了2个,所以追加是在第三个,也就是slice[2],len就修改为3.
注意已经存储的元素是可以安全读写的,也就是len的长度内。一旦超出这个范围,就属于越界访问了,会发生panic,比如slice[3] = 1。
这次来看看字符串类型的slice,不用make,来试试new
p := new([]string)

同样分配了这三部分结构,不负责底层数组的分配,所以data = nil,len = 0, cap = 0,new的返回值就是slice结构的起始地址
*p = append(*p, "hello")

通过append添加元素,给slice开辟底层数组,需要容纳一个字符串元素的数组,而字符串类型有俩部分组成,一个是起始地址,指向字符串内容,还有一个是字节长度
数组跟切片
arr := [10]int{0,1,2,3,4,5,6,7,8,9}
var s1 []int = arr[1,4]
var s2 []int = arr[7:]

定义变量arr整形数组,注意数组容量声明了就不能修改,可以把不同的slice关联在同一个数组上,共用底层数组.
s1的元素是arr索引1到4,左闭右开,这三个元素就添加到s1中了,但是容量是从data这里开始到底层数组结束共有9个元素。
s2的元素同理s1,元素为3,容量也是3.
如果给s2再添加元素会怎样?那这个arr底层数组是不能用了,得开辟新数组。
s2 = append(s2, 10)

拷贝原来的元素,添加新元素,元素个数改为4,容量扩到6.
扩容机制
这里就出现了问题,只给s2添加了一个元素,容量怎么从3变成6了?
那就要看slice的扩容规则了。
预估判断:
如果扩容前容量翻倍还是小于所需最小容量,那么预估容量就等于所需最小容量
否则如果扩容前元素个数小于1024,那就翻倍,如果大于等于1024,那就扩个1.25倍(这个1.25倍貌似有点不对,大家翻看源码看看)
if oldcap * 2 < cap {
newcap = cap
} else {
if oldlen < 1024 {
newcap = oldcap * 2
} else {
newcap = oldcap * 1.25
}
}
第一步:预估扩容后的容量
s := []int{1,2} //扩容前oldcap = 2
s = append(s,3,4,5) //预估扩容到5
2 * 2 < 5
所以s容量2翻倍了还是小于5,那预估容量就是5
第二步:预估容量只是元素个数,那需要占用多少内存呢?
用预估容量*元素类型大小,得到就是所需内存大小了。5 * 8 = 40
但不是直接分配这么多就可以了,go的内存管理模块已经申请一批内存.0, 8, 16, 32, 48, 64, 80, 96, 112, 128…
会去匹配足够大并最接近的大小,也就是48。
第三步:取实际容量个数
在上一步匹配到内存48字节可以装多少个元素呢?内存大小/元素类型大小
48 / 8 = 6
这个6就是扩容后的容量了
1418

被折叠的 条评论
为什么被折叠?



