Go的slice与数组类似,能够通过下标进行访问,越界访问时会报错。但slice比数组更加灵活,数组的大小是固定的,而slice可进行自动扩容。
一、数据结构
type slice struct {
array unsafe.Pointer
len int
cap int
}
可以看到,slice底层包含了一个指向数组的指针,以及slice的长度和容量,可通过len()和cap()获取
二、源码分析
1. makeSlice
首先看创建一个slice
package main
import "fmt"
func main() {
slice := make([]int, 5, 10)
fmt.Print(slice)
}
以上是一个简单的slice创建例子,通过执行以下指令,可获得Go汇编代码:
go tool compile -S main.go
执行上述指令后可看到以下输出,可见创建切片时调用了runtime包的makeSlice方法
// et为创建的slice元素类型,len为长度,cap为容量
func makeslice(et *_type, len, cap int) unsafe.Pointer {
// 首先计算创建一个cap容量的slice需要的空间
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
// 检查空间及传入的len、cap是否合法
if overflow || mem > maxAlloc || len < 0 || len > cap {
mem, overflow := math.MulUintptr(et.size, uintptr(len))
if overflow || mem > maxAlloc || len < 0 {
panicmakeslicelen()
}
panicmakeslicecap()
}
// 为slice分配内存并返回
return mallocgc(mem, et, true)
}
2. growslice
下面例子中首先创建了一个长度为0的slice,然后再向该切片增加一个元素
package main
import "fmt"
func main() {
slice := make([]int, 0)
slice = append(slice, 1)
fmt.Print(slice)
}
查看汇编代码发现调用append增加元素时调用了growslice方法
func growslice(et *_type, old slice, cap int) slice {
// 新的slice的cap不合法则panic
if cap < old.cap {
panic(errorString("growslice: cap out of range"))
}
if et.size == 0 {
// 如果当前切片大小为0,还需要扩容,则创建一个新容量的切片返回
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
// 新slice的cap计算规则
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
// 如果slice长度小于1024,则slice容量为原来的两倍
if old.len < 1024 {
newcap = doublecap
// 否则slice容量以0.25速率扩容直至大于等于期望的容量
} else {
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
if newcap <= 0 {
newcap = cap
}
}
}
// 新slice内存空间计算规则
var overflow bool
var lenmem, newlenmem, capmem uintptr
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 == sys.PtrSize:
lenmem = uintptr(old.len) * sys.PtrSize
newlenmem = uintptr(cap) * sys.PtrSize
capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
newcap = int(capmem / sys.PtrSize)
case isPowerOfTwo(et.size):
var shift uintptr
if sys.PtrSize == 8 {
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)
}
// 检查容量是否合法
if overflow || capmem > maxAlloc {
panic(errorString("growslice: cap out of range"))
}
// 分配内存
var p unsafe.Pointer
if et.ptrdata == 0 {
p = mallocgc(capmem, nil, false)
// 清空不需要拷贝数据的内存
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 {
bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata)
}
}
// 拷贝数据
memmove(p, old.array, lenmem)
return slice{p, old.len, newcap}
}