溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Golang中的Slice底層如何實現(xiàn)

發(fā)布時間:2023-02-27 09:12:30 來源:億速云 閱讀:100 作者:iii 欄目:開發(fā)技術

這篇“Golang中的Slice底層如何實現(xiàn)”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Golang中的Slice底層如何實現(xiàn)”文章吧。

    1 Go數(shù)組

    Go數(shù)組是值類型,數(shù)組定義的時候就需要指定大小,不同大小的數(shù)組是不同的類型,數(shù)組大小固定之后不可改變。數(shù)組的賦值和傳參都會復制一份。

    func main() {
        arrayA := [2]int{100, 200}
        var arrayB [2]int
    
        arrayB = arrayA
    
        fmt.Printf("arrayA : %p , %v\n", &arrayA, arrayA)
        fmt.Printf("arrayB : %p , %v\n", &arrayB, arrayB)
    
        testArray(arrayA)
    }
    
    func testArray(x [2]int) {
        fmt.Printf("func Array : %p , %v\n", &x, x)
    }

    結果:

    arrayA : 0xc4200bebf0 , [100 200]
    arrayB : 0xc4200bec00 , [100 200]
    func Array : 0xc4200bec30 , [100 200]

    可以看到,三個內存地址都不同,這也就驗證了 Go 中數(shù)組賦值和函數(shù)傳參都是值復制的。尤其是傳參的時候把數(shù)組復制一遍,當數(shù)組非常大的時候會非常消耗內存。可以考慮使用指針傳遞。

    指針傳遞有個不好的地方,當函數(shù)內部改變了數(shù)組的內容,則原數(shù)組的內容也改變了。

    因此一般參數(shù)傳遞的時候使用slice

    2 切片的數(shù)據(jù)結構

    切片本身并不是動態(tài)數(shù)組或者數(shù)組指針。它內部實現(xiàn)的數(shù)據(jù)結構通過指針引用底層數(shù)組,設定相關屬性將數(shù)據(jù)讀寫操作限定在指定的區(qū)域內。切片本身是一個只讀對象,其工作機制類似數(shù)組指針的一種封裝。

    切片(slice)是對數(shù)組一個連續(xù)片段的引用,所以切片是一個引用類型。這個片段可以是整個數(shù)組,或者是由起始和終止索引標識的一些項的子集。需要注意的是,終止索引標識的項不包括在切片內。切片提供了一個與指向數(shù)組的動態(tài)窗口。

    給定項的切片索引可能比相關數(shù)組的相同元素的索引小。和數(shù)組不同的是,切片的長度可以在運行時修改,最小為 0 最大為相關數(shù)組的長度:切片是一個長度可變的數(shù)組。

    切片數(shù)據(jù)結構定義

    type slice struct {
        array unsafe.Pointer
        len   int
        cap   int
    }

    Golang中的Slice底層如何實現(xiàn)

    切片的結構體由3部分構成,Pointer 是指向一個數(shù)組的指針,len 代表當前切片的長度,cap 是當前切片的容量。cap 總是大于等于 len 的。

    Golang中的Slice底層如何實現(xiàn)

    如果想從 slice 中得到一塊內存地址,可以這樣做:

    s := make([]byte, 200)
    ptr := unsafe.Pointer(&s[0])

    3 創(chuàng)建切片

    3.1 方法一:make

    使用make函數(shù)創(chuàng)建slice

    // 創(chuàng)建一個初始大小是3,容量是10的切片
    s1 := make([]int64,3,10)

    底層方法實現(xiàn):

    func makeslice(et *_type, len, cap int) slice {
        // 根據(jù)切片的數(shù)據(jù)類型,獲取切片的最大容量
        maxElements := maxSliceCap(et.size)
        // 比較切片的長度,長度值域應該在[0,maxElements]之間
        if len < 0 || uintptr(len) > maxElements {
            panic(errorString("makeslice: len out of range"))
        }
        // 比較切片的容量,容量值域應該在[len,maxElements]之間
        if cap < len || uintptr(cap) > maxElements {
            panic(errorString("makeslice: cap out of range"))
        }
        // 根據(jù)切片的容量申請內存
        p := mallocgc(et.size*uintptr(cap), et, true)
        // 返回申請好內存的切片的首地址
        return slice{p, len, cap}
    }

    3.2 方法二:字面量

    利用數(shù)組創(chuàng)建切片

    arr := [10]int64{1,2,3,4,5,6,7,8,9,10}
    s1 := arr[2:4:6] // 以arr[2:4]創(chuàng)建一個切片,且容量到達arr[6]的位置,即cap=6-2=4,如果不寫容量則默認為數(shù)組最后一個元素

    4 nil和空切片

    Golang中的Slice底層如何實現(xiàn)

    nil切片的指針指向的是nil

    Golang中的Slice底層如何實現(xiàn)

    空切片指向的是一個空數(shù)組

    空切片和 nil 切片的區(qū)別在于,空切片指向的地址不是nil,指向的是一個內存地址,但是它沒有分配任何內存空間,即底層元素包含0個元素。

    最后需要說明的一點是。不管是使用 nil 切片還是空切片,對其調用內置函數(shù) append,len 和 cap 的效果都是一樣的

    5 切片擴容

    5.1 擴容策略

    func main() {
        slice := []int{10, 20, 30, 40}
        newSlice := append(slice, 50)
        fmt.Printf("Before slice = %v, Pointer = %p, len = %d, cap = %d\n", slice, &slice, len(slice), cap(slice))
        fmt.Printf("Before newSlice = %v, Pointer = %p, len = %d, cap = %d\n", newSlice, &newSlice, len(newSlice), cap(newSlice))
        newSlice[1] += 10
        fmt.Printf("After slice = %v, Pointer = %p, len = %d, cap = %d\n", slice, &slice, len(slice), cap(slice))
        fmt.Printf("After newSlice = %v, Pointer = %p, len = %d, cap = %d\n", newSlice, &newSlice, len(newSlice), cap(newSlice))
    }

    輸出結果:

    Before slice = [10 20 30 40], Pointer = 0xc4200b0140, len = 4, cap = 4
    Before newSlice = [10 20 30 40 50], Pointer = 0xc4200b0180, len = 5, cap = 8
    After slice = [10 20 30 40], Pointer = 0xc4200b0140, len = 4, cap = 4
    After newSlice = [10 30 30 40 50], Pointer = 0xc4200b0180, len = 5, cap = 8

    Go 中切片擴容的策略是這樣的:

    如果切片的容量小于 1024 個元素,于是擴容的時候就翻倍增加容量。上面那個例子也驗證了這一情況,總容量從原來的4個翻倍到現(xiàn)在的8個。

    一旦元素個數(shù)超過 1024 個元素,那么增長因子就變成 1.25 ,即每次增加原來容量的四分之一。

    5.2 底層數(shù)組是不是新地址

    不一定。當發(fā)生了擴容就肯定是新數(shù)組,沒有發(fā)生擴容則是舊地址

    不管切片是通過make創(chuàng)建還是字面量創(chuàng)建,底層都是一樣的,指向的是一個數(shù)組。當使用字面量創(chuàng)建時,切片底層使用的數(shù)組就是創(chuàng)建時候的數(shù)組。修改切片中的元素或者往切片中添加元素,如果沒有擴容,則會影響原數(shù)組的內容,切片底層和原數(shù)組是同一個數(shù)組;當切片擴容了之后,則修改切片的元素或者往切片中添加元素,不會修改數(shù)組內容,因為切片擴容之后,底層數(shù)組不再是原數(shù)組,而是一個新數(shù)組。

    所以盡量避免切片底層數(shù)組與原始數(shù)組相同,盡量使用make創(chuàng)建切片

    range遍歷數(shù)組或者切片需要注意

    func main() {
    	// slice := []int{10, 20, 30, 40}
    	slice := [4]int{10, 20, 30, 40}
    	for index, value := range slice {
    		fmt.Printf("value = %d , value-addr = %x , slice-addr = %x\n", value, &value, &slice[index])
    	}
    }

    結果:

    value = 10 , value-addr = c00000a0a8 , slice-addr = c000012360
    value = 20 , value-addr = c00000a0a8 , slice-addr = c000012368
    value = 30 , value-addr = c00000a0a8 , slice-addr = c000012370
    value = 40 , value-addr = c00000a0a8 , slice-addr = c000012378

    從上面結果我們可以看到,如果用 range 的方式去遍歷一個數(shù)組或者切片,拿到的 Value 其實是切片里面的值拷貝。所以每次打印 Value 的地址都不變。

    Golang中的Slice底層如何實現(xiàn)

    由于 Value 是值拷貝的,并非引用傳遞,所以直接改 Value 是達不到更改原切片值的目的的,需要通過 &slice[index] 獲取真實的地址

    尤其是在for循環(huán)中使用協(xié)程,一定不能直接把index,value傳入?yún)f(xié)程,而應該通過參數(shù)傳進去

    錯誤示例:

    func main() {
    	s := []int{10,20,30}
    	for index, value := range s {
    		go func() {
    			time.Sleep(time.Second)
    			fmt.Println(fmt.Sprintf("index:%d,value:%d", index,value))
    		}()
    	}
    	time.Sleep(time.Second*2)
    }

    結果:

    index:2,value:30
    index:2,value:30
    index:2,value:30

    正確示例:

    func main() {
    	s := []int{10,20,30}
    	for index, value := range s {
    		go func(i,v int) {
    			time.Sleep(time.Second)
    			fmt.Println(fmt.Sprintf("index:%d,value:%d", i,v))
    		}(index,value)
    	}
    	time.Sleep(time.Second*2)
    }

    結果:

    index:0,value:10
    index:2,value:30
    index:1,value:20

    以上就是關于“Golang中的Slice底層如何實現(xiàn)”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業(yè)資訊頻道。

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

    AI