切片的底层其实也是一个结构体,结构体定义如下
切片的结构体由3部分构成,Pointer 是指向一个数组的指针,len 代表当前切片的长度,cap 是当前切片的容量。cap 总是大于等于 len 的。
切片的本质是对数组的引用
切片的创建
1,根据数组创建
arr[0:3] or slice[0:3]
2,字面量:编译时插和创建
当使用形如[]int{1,2,3}的字面量创建新的切片时,会创建一个array数组([3]int{1,2,3})存储于静态区中,并在堆区创建一个新的切片,在程序启动时将静态区的数据复制到堆区,这样可以加快切片的初始化过程
3.make:运行时创建数组
切片的扩容
切片的扩容分为两种
一种是不扩容时,只调整len,编译器负责
切片使用append函数添加元素,但不是使用了append函数就需要进行扩容,如下代码向长度为3,容量为4的切片a中添加元素后不需要。
另一种
例如:切片增加元素后长度超过了现有容量,例如b一开始的长度和容量
都为3,但使用append函数后,其容量变为了6。
append函数在运行时调用了runtime/slice.go文件下的growslice函数
unc growslice(et *_type, old slice, cap int) slice { if raceenabled { callerpc := getcallerpc() racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, abi.FuncPCABIInternal(growslice)) } if msanenabled { msanread(old.array, uintptr(old.len*int(et.size))) } if asanenabled { asanread(old.array, uintptr(old.len*int(et.size))) } if cap < old.cap { panic(errorString("growslice: cap out of range")) } if et.size == 0 { // append should not create a slice with nil pointer but non-zero len. // We assume that append doesn't need to preserve old.array in this case. return slice{unsafe.Pointer(&zerobase), old.len, cap} } newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { const threshold = 256 if old.cap < threshold { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { // Transition from growing 2x for small slices // to growing 1.25x for large slices. This formula // gives a smooth-ish transition between the two. newcap += (newcap + 3*threshold) / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } } } var overflow bool var lenmem, newlenmem, capmem uintptr // Specialize for common values of et.size. // For 1 we don't need any division/multiplication. // For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant. // For powers of 2, use a variable shift. switch { case et.size == 1: lenmem = uintptr(old.len) newlenmem = uintptr(cap) capmem = roundupsize(uintptr(newcap)) overflow = uintptr(newcap) > maxAlloc newcap = int(capmem) case et.size == goarch.PtrSize: lenmem = uintptr(old.len) * goarch.PtrSize newlenmem = uintptr(cap) * goarch.PtrSize capmem = roundupsize(uintptr(newcap) * goarch.PtrSize) overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize newcap = int(capmem / goarch.PtrSize) case isPowerOfTwo(et.size): var shift uintptr if goarch.PtrSize == 8 { // Mask shift for better code generation. shift = uintptr(sys.Ctz64(uint64(et.size))) & 63 } else { shift = uintptr(sys.Ctz32(uint32(et.size))) & 31 } lenmem = uintptr(old.len) << shift newlenmem = uintptr(cap) << shift capmem = roundupsize(uintptr(newcap) << shift) overflow = uintptr(newcap) > (maxAlloc >> shift) newcap = int(capmem >> shift) default: lenmem = uintptr(old.len) * et.size newlenmem = uintptr(cap) * et.size capmem, overflow = math.MulUintptr(et.size, uintptr(newcap)) capmem = roundupsize(capmem) newcap = int(capmem / et.size) } // The check of overflow in addition to capmem > maxAlloc is needed // to prevent an overflow which can be used to trigger a segfault // on 32bit architectures with this example program: // // type T [1<<27 + 1]int64 // // var d T // var s []T // // func main() { // s = append(s, d, d, d, d) // print(len(s), "\n") // } if overflow || capmem > maxAlloc { panic(errorString("growslice: cap out of range")) } var p unsafe.Pointer if et.ptrdata == 0 { p = mallocgc(capmem, nil, false) // The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length). // Only clear the part that will not be overwritten. memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem) } else { // Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory. p = mallocgc(capmem, et, true) if lenmem > 0 && writeBarrier.enabled { // Only shade the pointers in old.array since we know the destination slice p // only contains nil pointers because it has been cleared during alloc. bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata) } } memmove(p, old.array, lenmem) return slice{p, old.len, newcap} }
Go语言中切片扩容的策略为:
◎ 如果新申请容量(cap)大于2倍的旧容量(old.cap),则最终容量(newcap)是新申请的容量(cap)。
◎ 如果旧切片的长度小于256,则最终容量是旧容量的2倍,即newcap=doublecap。
◎ 如果旧切片长度大于或等于256,则最终容量从旧容量开始循环增加原来的1/4,即newcap=old.cap,for {newcap+=newcap/4},直到最终容量大于或等于新申请的容量为止,即newcap ≥ cap。
◎ 如果最终容量计算值溢出,即超过了int的最大范围,则最终容量就是新申请容量。
注:切片扩容时,并发不安全,注意切片并发要加锁