參考文章:
https://studygolang.com/articles/18194?fr=sidebar
https://github.com/cch123/golang-notes/blob/master/slice.md
https://blog.haohtml.com/archives/18094
slice:
先看一下slcie結構:
runtime/slice.go
type slice struct {
array unsafe.Pointer
len int
cap int
}
slice三要素: type、len、cap
slice 的底層結構定義非常直觀,指向底層數組的指針,當前長度 len 和當前 slice 的 cap。
[]int{1,3,4,5}
struct {
array unsafe.Pointer --------------+
len int |
cap int |
} |
|
v
+------|-------|------|------+-----+
| | 1 | 3 | 4 | 5 |
| | | | | |
+------|-------|------|------+-----+
[5]int
func makeslice(et *_type, len, cap int) slice
slice擴容機制:
擴容時會判斷 slice 的 cap 是不是已經大於 1024,如果在 1024 之內,會按二倍擴容。超過的話就是 1.25 倍擴容了。
slice 擴容必然會導致內存拷貝,如果是性能敏感的系統中,儘可能地提前分配好 slice 是較好的選擇。
var arr = make([]int, 0, 10)
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}
}
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
// 注意這裏的 1024 閾值
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
}
}
}
var overflow bool
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))
overflow = uintptr(newcap) > _MaxMem
newcap = int(capmem)
case ptrSize:
lenmem = uintptr(old.len) * ptrSize
newlenmem = uintptr(cap) * ptrSize
capmem = roundupsize(uintptr(newcap) * ptrSize)
overflow = uintptr(newcap) > _MaxMem/ptrSize
newcap = int(capmem / ptrSize)
default:
lenmem = uintptr(old.len) * et.size
newlenmem = uintptr(cap) * et.size
capmem = roundupsize(uintptr(newcap) * et.size)
overflow = uintptr(newcap) > maxSliceCap(et.size)
newcap = int(capmem / et.size)
}
if cap < old.cap || overflow || capmem > _MaxMem {
panic(errorString("growslice: cap out of range"))
}
var p unsafe.Pointer
if et.kind&kindNoPointers != 0 {
p = mallocgc(capmem, nil, false)
memmove(p, old.array, lenmem)
// 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 !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}
}
slice的append()函數
func append(slice []Type, elems ...Type) []Type
函數說明:內建函數append追加一個或多個elems到一個slice依賴的array的末尾,如果這個slice有足夠的capacity,則reslice以容納新增元素;如果capacity空間不夠,則進行擴容,重新分配內存保存新的slice依賴的array,函數返回更新後的slice.
注意:append不會修改傳參進來的slice(len和cap),只會在不夠用的時候新分配一個array,並把之前的slice依賴的array數據拷貝過來;所以對同一個slice 重複 append,只要不超過cap,都是修改的同一個array,後面的會覆蓋前面
slice是做函數參數是值傳遞:
func main() {
var a = make([]int, 10)
fmt.Println(a)
}
func doSomeHappyThings(sl []int) {
if len(sl) > 0 {
sl[0] = 1
}
}
把 a 傳入到 doSomeHappyThings,然後 a 的第一個元素就被修改了,進而認爲在 Go 中,slice 是引用傳遞的。其實這是錯誤,go裏面都是值傳遞,不存在引用傳遞。