go语言切片系列(二)

1,747 阅读2分钟

「这是我参与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)