「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战」。
前言
上篇文章简单的介绍了slice的特点,这篇文章更深入的介绍slice。
初始化
和之前介绍的go语言其他数据结构一样,当初始化的切片大小超过64KB时,则将切片分配到堆中。如果切片的大小小于64KB,则将切片分配到栈上,另外,编译时可以指定
SmallFrames参数,这个参数是一个布尔值,可以指定在容量较小的时候是否将其分配到栈上。
扩容
切片的扩容的核心逻辑在growslice中,这个函数的作用是传入元素类型,旧有切片,所需的新容量,在内存中申请一个新的切片,并将旧切片复制其中。
这里代码有个有趣的地方,设计者甚至预先考虑过拓展一个空指针的情况
if et.size == 0 {
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
接下来是容量的计算
if cap > doublecap {
newcap = cap
} else {
if old.cap < 1024 {
newcap = doublecap
} else {
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
这里需要分清三个概念:旧容量、所需容量、新容量。
- 如果所需容量大于二倍旧容量,则设置为所需容量,
- 如果旧容量小于1024,则设置为二倍旧容量(老实说到这和Java挺像的)
- 不断循环让旧有容量增加25%,知道他超过所需容量
-
如果溢出,那就算了(指直接设置为所需容量)
-
即使是确定了容量,由于对齐内存等原因,go语言还会调整切片的大小,因此一般会大于等于算好的容量*元素size。
当切片类型不是指针的时候,会将新的地址后面的内存清除,然后会使用memmove将旧切片移动到新内存中。
var p unsafe.Pointer
if et.ptrdata == 0 {
p = mallocgc(capmem, nil, false)
memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
}else{}
memmove(p, old.array, lenmem)