Slice
Slice依托数组实现,底层数组对用户屏蔽,在底层数组容量不足时可以实现自动重分配并生成新的Slice。接下来按照实际使用场景分别介绍其实现机制。
Slice数据结构
源码包中src/runtime/slice.go:slice定义了Slice的数据结构:
type slice struct {
array unsafe.Pointer
len int
cap int
}
从数据结构看Slice很清晰, array指针指向底层数组,len表示切片长度,cap表示底层数组容量。
Slice 扩容
使用append向Slice追加元素时,如果Slice空间不足,将会触发Slice扩容,扩容实际上重新一配一块更大的内存,将原Slice数据拷贝进新Slice,然后返回新Slice,扩容后再将数据追加进去。
golang slice (切片) 扩容机制详解(1.18版本后)
slice源码定义:
type slice struct {
array unsafe.Pointer //指向底层数组的指针
len int //切片长度
cap int //切片容量
}
growslice()方法:用于 slice 的扩容
func growslice(et *_type, old slice, cap int) slice {
// ......
newcap := old.cap
doublecap := newcap + newcap //双倍扩容(原容量的两倍)
if cap > doublecap { //如果所需容量大于 两倍扩容,则直接扩容到所需容量
newcap = cap
} else {
const threshold = 256 //这里设置了一个 阈值 -- 256
if old.cap < threshold { //如果旧容量 小于 256,则两倍扩容
newcap = doublecap
} else {
// 检查 0 < newcap 以检测溢出并防止无限循环。
for 0 < newcap && newcap < cap { //如果新容量 > 0 并且 原容量 小于 所需容量
// 从小片的增长2x过渡到大片的增长1.25x。这个公式给出了两者之间的平滑过渡。
newcap += (newcap + 3*threshold) / 4
//新容量是 = 1.25 原容量 + 3/4 阈值 (192)
//当newcap计算溢出时,将newcap设置为请求的上限。
if newcap <= 0 { // 如果发生了溢出,将新容量设置为请求的容量大小
newcap = cap
}
}
}
}
具体情况如下:
-
如果请求容量 大于 两倍现有容量 ,则新容量 直接为请求容量
-
否则(请求容量 小于等于 两倍现有容量)
如果 现有容量 小于 256 ,则新容量是原来的两倍
否则:新容量 = 1.25 原容量 + 3/4 阈值 (192) “这个公式给出了从1.25倍增长 过渡到2 倍增长,两者之间的平滑过渡。” 在此情况下,如果发生了溢出,将新容量设置为请求的容量大小
扩容容量的选择遵循以下规则:
在 1.18 版本前,切片扩容,在容量小于1024时,以2倍大小扩容。超过1024后,以1.25倍扩容。
在1.18版本后,切片扩容,在容量小于256时,以2倍大小扩容。超过256后,以(1.25倍+192)扩容。
使用append()向Slice添加一个元素的实现步骤如下:
- 假如Slice容量够用,则将新元素追加进去,Slice.len++,返回原Slice
- 原Slice容量不够,则将Slice先扩容,扩容后得到新Slice,再将新元素追加进新Slice,Slice.len++,返回新的Slice。