因爲 Go的 Array建立以後長度就固定了,所以咱們會更經常使用到 Slice。 在本篇裏咱們將從Slice的建立,訪問,增長,複製幾個方面來簡單瞭解下Slice的原理。golang
下面是Slice在runtime的結構體數組
type slice struct {
array unsafe.Pointer // 指向切片頭
len int // 切片吧長度
cap int // 切片容積
}
複製代碼
在編譯時的Phase 6階段,會進行逃逸分析,若是Slice發生逃逸或很是大時,會調用makeSlice64在堆上建立,不然就會在棧上或者靜態存儲區建立Slice。app
case OMAKESLICE:
...
if n.Esc == EscNone {
對於在棧上的將會轉成如下相似代碼
// var arr [r]T
// n = arr[:l]
...
}else {
對於在堆上建立的,將會調用makeslice64
...
fnname := "makeslice64"
...
m.Left = mkcall1(fn, types.Types[TUNSAFEPTR], init, typename(t.Elem()), conv(len, argtype), conv(cap, argtype))
...
}
複製代碼
func makeslice(et *_type, len, cap int) unsafe.Pointer {
校驗cap長度
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
if overflow || mem > maxAlloc || len < 0 || len > cap {
// NOTE: Produce a 'len out of range' error instead of a
// 'cap out of range' error when someone does make([]T, bignumber).
// 'cap out of range' is true too, but since the cap is only being
// supplied implicitly, saying len is clearer.
// See golang.org/issue/4085.
mem, overflow := math.MulUintptr(et.size, uintptr(len))
if overflow || mem > maxAlloc || len < 0 {
panicmakeslicelen()
}
panicmakeslicecap()
}
分配內存
return mallocgc(mem, et, true)
}
複製代碼
Slice的訪問在編譯時就已經轉化成了彙編代碼,因爲Slice的結構中的array指向的數組頭的地址,並且Slice是一片連續的地址,訪問就是從頭日後數,具體邏輯在編譯時的typecheck1的OINDEX中。函數
func typecheck1(n *Node, top int) (res *Node) {
...
switch n.Op {
case OINDEX:
...
switch t.Etype {
case TSTRING, TARRAY, TSLICE:
n.Right = indexlit(n.Right)
if t.IsString() {
n.Type = types.Bytetype
} else {
n.Type = t.Elem()
}
why := "string"
if t.IsArray() {
why = "array"
} else if t.IsSlice() {
why = "slice"
}
...
}
}
複製代碼
Slice的插入,刪除等操做其實都是經過append函數來進行的,在append中,會通過一系列轉化以後,調用到runtime的growslice方法,growslice會經過容量的大小進行不一樣的擴容策略,最後在進行遷移。oop
func growslice(et *_type, old slice, cap int) slice {
...
經過容量進行擴容選擇,減小頻繁擴容以後進行遷移
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
...
memmove(p, old.array, lenmem)
return slice{p, old.len, newcap}
}
複製代碼
切片的複製是Slice在運行時裏有實現,在編譯時裏,會經過HasHeapPointer來判斷Slice是否存在堆上,是就會進行slicecopy操做,不是的話就會調用memmove對內存進行復制。ui
func copyany(n *Node, init *Nodes, runtimecall bool) *Node {
判斷是否實在堆上建立
if n.Left.Type.Elem().HasHeapPointer() {
Curfn.Func.setWBPos(n.Pos)
fn := writebarrierfn("typedslicecopy", n.Left.Type, n.Right.Type)
return mkcall1(fn, n.Type, init, typename(n.Left.Type.Elem()), n.Left, n.Right)
}
....
fn := syslook("memmove")
fn = substArgTypes(fn, nl.Type.Elem(), nl.Type.Elem())
}
複製代碼
func slicecopy(to, fm slice, width uintptr) int {
if fm.len == 0 || to.len == 0 {
return 0
}
n := fm.len
if to.len < n {
n = to.len
}
if width == 0 {
return n
}
if raceenabled {
callerpc := getcallerpc()
pc := funcPC(slicecopy)
racewriterangepc(to.array, uintptr(n*int(width)), callerpc, pc)
racereadrangepc(fm.array, uintptr(n*int(width)), callerpc, pc)
}
if msanenabled {
msanwrite(to.array, uintptr(n*int(width)))
msanread(fm.array, uintptr(n*int(width)))
}
經過一系列判斷最後走下面的if進行復制
size := uintptr(n) * width
if size == 1 { // common case worth about 2x to do here
// TODO: is this still worth it with new memmove impl?
*(*byte)(to.array) = *(*byte)(fm.array) // known to be a byte pointer
} else {
memmove(to.array, fm.array, size)
}
return n
}
複製代碼