首頁  >  文章  >  後端開發  >  閱讀Golang 切片(Slice)底層源碼

閱讀Golang 切片(Slice)底層源碼

藏色散人
藏色散人轉載
2021-03-03 15:51:362898瀏覽
##上下上由

go語言##1欄位中給大家介紹Golang 切片(Slice)底層原始碼,希望對需要的朋友有所幫助!

閱讀Golang 切片(Slice)底層源碼

陣列

說切片前先說下陣列。陣列的兩個特性

    一段連續記憶體位址,每個元素都是連續的
  • 元素的型別相同,並且元素個數固定
#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 的資料結構定義如下:

runtime/slice.go#L13

type slice struct {
    array unsafe.Pointer    len   int
    cap   int}
    array 是底層陣列的位址
  • len 切片的長度
  • cap 切片的容量

建立切片

src/runtime/slice.go#L83

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 6fcbe6a181235a2d91480a84b8bdbb32 doublecap {
        newcap = cap
    } else {
    // 旧容量 小于1024,新容量= 旧容量 * 2 也就是扩容1倍
        if old.cap c9221202ba118f4c45c4e2fcde42ff0d 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
  • 如果數值溢出,要擴容的容量= 新申請的容量
  •   arr := 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
    注意事項: 切片共享底層數組,所以在切片賦值的時候,修改切片會導致底層數組改變,而產生BUG
  • #
    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)
切片複製

src/runtime/slice.go#L247

//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
}


以上是閱讀Golang 切片(Slice)底層源碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:learnku.com。如有侵權,請聯絡admin@php.cn刪除