陣列
說切片前先說下陣列。陣列的兩個特性
- 一段連續記憶體地址,每個元素都是連續的
- 元素的型別相同,並且元素個數固定
Go 陣列是值型別,賦值和函式傳參操作都會複製整個陣列資料。
arr := [2]int{1,2}
arr2 := arr
fmt.Printf("%p %p",&arr ,&arr2)
//切片
slice1 := []int{1,2}
slice2 := slice1
fmt.Printf("%p %p",slice1 ,slice2)
切片
切片(slice)是對陣列一個連續片段的引用,所以切片是一個引用型別.切片是一個長度可變的陣列。
Slice 的資料結構定義如下:
type slice struct {
array unsafe.Pointer
len int
cap int
}
- array 就是底層陣列的地址
- len 切片的長度
- cap 切片的容量
建立切片
func makeslice(et *_type, len, cap int) unsafe.Pointer {
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
....
return mallocgc(mem, et, true)
}
基本邏輯就是根據容量申請一塊記憶體。
切片擴容
擴容是當切片的長度大於容量的時候,底層陣列已經裝不下時
func growslice(et *_type, old slice, cap int) slice {
...
// 如果新要擴容的容量比原來的容量還要小,直接報panic
if cap < old.cap {
panic(errorString("growslice: cap out of range"))
}
// 如果當前切片的大小為0,還呼叫了擴容方法,那麼就新生成一個新的容量的切片返回
// []struct{}
if et.size == 0 {
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
newcap := old.cap
doublecap := newcap + newcap
//要擴容的容量大於2 *oldcap 新切片容量 = 該容量
if cap > doublecap {
newcap = cap
} else {
// 舊容量 小於1024,新容量= 舊容量 * 2 也就是擴容1倍
if old.cap < 1024 {
newcap = doublecap
} else {
// 擴容容量 = 舊容量 +舊容量*1/4
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
//溢位之後 新容量=要擴容的容量
if newcap <= 0 {
newcap = cap
}
}
}
var overflow bool
// 計算新的切片的容量,長度。
var lenmem, newlenmem, capmem uintptr
....
var p unsafe.Pointer
if et.ptrdata == 0 {
p = mallocgc(capmem, nil, false)
memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
} else {
p = mallocgc(capmem, et, true)
if lenmem > 0 && writeBarrier.enabled {
bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata)
}
}
//移動到p
memmove(p, old.array, lenmem)
//返回slice結構,讓slice.array指向p
return slice{p, old.len, newcap}
}
- 新申請容量cap,如果大於2倍舊容量(oldcap),要擴容的容量(newcap)=新申請容量cap
- 如果舊容量(oldcap)< 1024, 要擴容的容量(newcap)在舊容量(oldcap)基礎上擴容1倍,否則則擴容 1/4
- 如果數值溢位,要擴容的容量 = 新申請的容量
注意事項: 切片共享底層陣列,所以在切片賦值的時候,修改切片會導致底層陣列改變,而產生BUGarr := make([]int,1024) arr = append(arr,1) fmt.Println(len(arr),cap(arr))// 1025,1280 arr1 := make([]int,10) arr1 = append(arr1,1) fmt.Println(len(arr1),cap(arr1))//11 20
arr := []int{1,2,3,4} arr1 := arr[:2] //[1,2] arr1 = append(arr1,5) fmt.Println(arr[3]) //5 修改了底層陣列 //例子2 arr3 := []int{1,2,3,4} arr4 := arr3[2:] arr4 = append(arr4,10)//擴容 不會影響arr3 fmt.Println(arr3)
切片複製
//toPtr 目標地址 toLen目標長度
// width 元素大小
func slicecopy(toPtr unsafe.Pointer, toLen int, fromPtr unsafe.Pointer, fromLen int, width uintptr) int {
//判斷長度
if fromLen == 0 || toLen == 0 {
return 0
}
n := fromLen
if toLen < n {
n = toLen
}
//切片大小等於0
if width == 0 {
return n
}
size := uintptr(n) * width
//特殊處理 如果只有一個元素並且大小是1byte,那麼指標直接轉換即可
if size == 1 {
*(*byte)(toPtr) = *(*byte)(fromPtr)
} else {
//從 fm.array 地址開始,拷貝到 to.array 地址之後
memmove(toPtr, fromPtr, size)
}
return n
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結