首先我們來(lái)看段代碼的輸出
s := make([][]int, 4)
for i := 0; i < 4; i++ {
s[i] = make([]int, 4)
s[i][0] = 1
}
s0 := s[0]
s0 = append(s0, 5)
fmt.Println(s)
輸出的結(jié)果是
[[1 0 0 0] [1 0 0 0] [1 0 0 0] [1 0 0 0]]
append的值5并沒(méi)有輸出,那么究竟是s0并不等價(jià)于s[0],還是有其他原因呢?
首先,肯定的是在Go中,所有的拷貝都是值拷貝,不存在引用拷貝;
其次,slice由一個(gè)指針,兩個(gè)整型(分別是size和cap)來(lái)實(shí)現(xiàn),既然s0包含的指針和s[0]包含的指針相同,為什么append操作并沒(méi)有達(dá)到預(yù)期效果呢?
帶著這些疑問(wèn),我們來(lái)看看slice的底層實(shí)現(xiàn)。
slice的結(jié)構(gòu)
type slice struct {
array unsafe.Pointer
len int
cap int
}
array指向一個(gè)數(shù)組元素的地址,這個(gè)數(shù)組可能在makeslice時(shí)創(chuàng)建,也可能之前就存在,而slice被"attach"上去,例如 s := a[0:5];
//代碼比較簡(jiǎn)單
func makeslice(et *_type, len, cap int) slice {
maxElements := maxSliceCap(et.size)
if len < 0 || uintptr(len) > maxElements {
panic(errorString("makeslice: len out of range"))
}
if cap < len || uintptr(cap) > maxElements {
panic(errorString("makeslice: cap out of range"))
}
//分配數(shù)組空間
p := mallocgc(et.size*uintptr(cap), et, true)
return slice{p, len, cap}
}
e.g.
s := make([]int, 1, 3)
fmt.Printf("%p, %v, len:%d, cap:%d", unsafe.Pointer(&s[0]), s, len(s), cap(s))
輸出:
0xc42007c0a0, [0], len:1, cap:3
對(duì)于直接"attach"到數(shù)組的情形,類似下面這樣
a := [10]int{0,1,2,3,4,5,6,7,8,9}
fmt.Printf("%p, %v \n", &a, a)
s1 := a[1:5:7]
fmt.Printf("%p, %v, len:%d, cap:%d, self:%p \n", unsafe.Pointer(&s1[0]), s1, len(s1), cap(s1), unsafe.Pointer(&s1) )
輸出:
0xc42006e050, [0 1 2 3 4 5 6 7 8 9]
0xc42006e058, [1 2 3 4], len:4, cap:6, self:0xc42006c140
根據(jù)兩個(gè)指針的關(guān)系,可以看出,slice直接指向了數(shù)組中的元素

同理,slice2還可以通過(guò)另一個(gè)slice1構(gòu)造,但其屬性依賴slice1,并不是slice1底層的數(shù)組
s2 := s1[2:]
fmt.Printf("%p, %v, len:%d, cap:%d, self:%p \n", unsafe.Pointer(&s2[0]), s2, len(s2), cap(s2), unsafe.Pointer(&s2) )
輸出
0xc42006e068, [3 4], len:2, cap:4, self:0xc42006a140

修改slice中元素的值,實(shí)際上修改的是底層數(shù)組元素的值
s2[0] = 100
fmt.Println(a, s1, s2)
輸出
[0 1 2 100 4 5 6 7 8 9] [1 2 100 4] [100 4]
擴(kuò)張
當(dāng)我們對(duì)slice進(jìn)行append操作時(shí),若len + 追加元素個(gè)數(shù) <= cap時(shí),不會(huì)發(fā)生內(nèi)存擴(kuò)張;否則,新的內(nèi)存被申請(qǐng),同時(shí)舊的數(shù)據(jù)被拷貝至新內(nèi)存的前部;
先上代碼
func growslice(et *_type, old slice, cap int) slice {
......
if et.size == 0 {
if cap < old.cap {
panic(errorString("growslice: cap out of range"))
}
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
// 計(jì)算新的cap,針對(duì)不同情況分別處理
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
//2倍
newcap = doublecap
} else {
//每次嘗試擴(kuò)1/4
for newcap < cap {
newcap += newcap / 4
}
}
}
var lenmem, newlenmem, capmem uintptr
const ptrSize = unsafe.Sizeof((*byte)(nil))
switch et.size {
case 1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
newcap = int(capmem)
case ptrSize:
lenmem = uintptr(old.len) * ptrSize
newlenmem = uintptr(cap) * ptrSize
capmem = roundupsize(uintptr(newcap) * ptrSize)
newcap = int(capmem / ptrSize)
default:
lenmem = uintptr(old.len) * et.size
newlenmem = uintptr(cap) * et.size
capmem = roundupsize(uintptr(newcap) * et.size)
newcap = int(capmem / et.size)
}
......
var p unsafe.Pointer
if et.kind&kindNoPointers != 0 {
//這里有個(gè)優(yōu)化細(xì)節(jié),先不zero,因?yàn)榍安繒?huì)發(fā)生覆蓋
p = mallocgc(capmem, nil, false)
memmove(p, old.array, lenmem)
//只對(duì)后部zero
memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
} else {
p = mallocgc(capmem, et, true)
if !writeBarrier.enabled {
memmove(p, old.array, lenmem)
} else {
for i := uintptr(0); i < lenmem; i += et.size {
typedmemmove(et, add(p, i), add(old.array, i))
}
}
}
return slice{p, old.len, newcap}
}
當(dāng)不需要擴(kuò)容時(shí),append會(huì)修改底層數(shù)組的值
s2 = append(s2, 1000)
fmt.Printf("%p, %v \n", unsafe.Pointer(&s2[0]), s2)
fmt.Println(a, s1, s2)
fmt.Printf("len(s1)=%d, cap(s1)=%d, len(s2)=%d, cap(s2)=%d", len(s1), cap(s1), len(s2), cap(s2))
輸出
[0 1 2 100 4 1000 6 7 8 9] [1 2 100 4] [100 4 1000]
len(s1)=4, cap(s1)=6, len(s2)=3, cap(s2)=4
注意,對(duì)s2的append,僅改變了s2的len,并沒(méi)有改變s1的len
當(dāng)append過(guò)多時(shí),slice底層數(shù)組會(huì)發(fā)生變化
s2 = append(s2, 1000, 1001, 1002)
fmt.Printf("%p, %v \n", unsafe.Pointer(&s2[0]), s2)
fmt.Println(a, s1, s2)
fmt.Printf("len(s1)=%d, cap(s1)=%d, len(s2)=%d, cap(s2)=%d", len(s1), cap(s1), len(s2), cap(s2))
輸出
0xc420014280, [100 4 1000 1001 1002]
[0 1 2 100 4 5 6 7 8 9] [1 2 100 4] [100 4 1000 1001 1002]
len(s1)=4, cap(s1)=6, len(s2)=5, cap(s2)=8
原數(shù)組a,切片s1的屬性未受影響;但s2底層的數(shù)組已發(fā)生變化,cap也是之前的2倍。
總結(jié)
1、多個(gè)slice指向相同的底層數(shù)組時(shí),修改其中一個(gè)slice,可能會(huì)影響其他slice的值;
2、slice作為參數(shù)傳遞時(shí),比數(shù)組更為高效,因?yàn)閟lice的結(jié)構(gòu)比較?。?br>
3、slice在擴(kuò)張時(shí),可能會(huì)發(fā)生底層數(shù)組的變更及內(nèi)存拷貝;
4、因?yàn)閟lice引用了數(shù)組,這可能導(dǎo)致數(shù)組空間不會(huì)被gc,當(dāng)數(shù)組空間很大,而slice引用內(nèi)容很少時(shí)尤為嚴(yán)重;