關(guān)于Golang切片Slice和append的有趣問題

開局一道題

請大家猜猜打印x和y的內(nèi)容會(huì)是什么?以及想想為什么會(huì)這樣子?其中的知識點(diǎn)有哪些?

package main

import (
    "fmt"
)

func main() {
    x := []int{1, 2, 3}
    y := x[:2]
    y = append(y, 10)
    fmt.Println("x=", x, &x[0])
    fmt.Println("y=", y, &y[0])

    y = append(y, 20)
    fmt.Println("y=", y, &y[0])
    y[0] = 20
    for i := 0; i < 10; i++ {
        y = append(y, i)
    }
    fmt.Println("y=", y, &y[0])
}

答案

package main

import (
    "fmt"
)

func main() {
    x := []int{1, 2, 3}
    y := x[:2]//【1】
    y = append(y, 10)//【2】
    fmt.Println("x=", x, &x[0])    //x= [1 2 10] 0xc0000b6020
    fmt.Println("y=", y, &y[0])    //y= [1 2 10] 0xc0000b6020
    y = append(y, 20)//【3】
    fmt.Println("y=", y, &y[0])    //y= [1 2 10 20] 0xc0000ac030
    y[0] = 20//【4】
    for i := 0; i < 10; i++ {
        y = append(y, i)
    }
    fmt.Println("y=", y, &y[0])    //y= [20 2 10 20 0 1 2 3 4 5 6 7 8 9] 0xc0000ba000 【5】
}

解釋

  • 【1】因?yàn)閥是x的slice切片{1,2},所以y和x指向的內(nèi)存地址是一樣的;
  • 【2】因?yàn)閥指向的內(nèi)存地址和x是一樣的,在尾部append一個(gè)值的時(shí)候,會(huì)擠掉后面的值3,故這時(shí)候x和y都為1,2,10
  • 【3】這時(shí)候y又再次appned,超出了原來的大小3,這時(shí)候會(huì)會(huì)分配一個(gè)更大數(shù)組來容納,會(huì)新建一塊獨(dú)立的內(nèi)存地址給到y(tǒng)(y獨(dú)立了,和x沒有什么關(guān)系了)。故y為1,2,10,20,x還是為1,2,10
  • 【4】由于y已指向全新的內(nèi)存地址,改變下標(biāo)為0的值為10,則y為20,2,10,20
  • 【5】slice擴(kuò)容,新開辟一塊更大內(nèi)存,把之前的數(shù)據(jù)復(fù)制過去,則y指向地址變化了

知識點(diǎn)

Slice實(shí)現(xiàn)原理

Slice結(jié)構(gòu).png
type Slice struct {
    ptr   unsafe.Pointer        // Array pointer
    len   int                   // slice length
    cap   int                 // slice capacity
}

slice 的數(shù)據(jù)結(jié)構(gòu),一個(gè)指向真實(shí) array 地址的指針 ptr ,slice 的長度 len 和容量 cap ,在底層數(shù)組容量不足時(shí)可以實(shí)現(xiàn)自動(dòng)重分配并生成新的Slice,在實(shí)際使用中,我們最好事先預(yù)期好一個(gè)cap,這樣在使用append的時(shí)候可以避免反復(fù)重新分配內(nèi)存復(fù)制之前的數(shù)據(jù),減少不必要的性能消耗。


Slice結(jié)構(gòu)2.png

舉例

package main

import (
    "fmt"
)

func main() {
    var data = make([]int, 1, 1)
    fmt.Println("data=", data, &data[0])
    data = append(data, 1)
    fmt.Println("data=", data, &data[0])
    for i := 0; i < 10; i++ {
        data = append(data, i)
        fmt.Println("i=%d,data=%v\n", i, &data[0])

    }
}

結(jié)果

data= [0] 0xc0000b4008 //【1】初始化地址
data= [0 1] 0xc0000b4030    //【2】第一次擴(kuò)容+1,地址改變
i=%d,data=%v
 0 0xc0000b6040             //【3】第二次擴(kuò)容+2,地址改變
i=%d,data=%v
 1 0xc0000b6040             //Slice容量夠用,則將新元素追加進(jìn)去,Slice.len++,返回原Slice
i=%d,data=%v
 2 0xc0000ba000             //【4】第三次擴(kuò)容+4,地址改變
i=%d,data=%v
 3 0xc0000ba000             //Slice容量夠用,則將新元素追加進(jìn)去,Slice.len++,返回原Slice
i=%d,data=%v
 4 0xc0000ba000             //同上
i=%d,data=%v
 5 0xc0000ba000             //同上
i=%d,data=%v
 6 0xc0000bc000             //【5】第四次擴(kuò)容+8,地址改變   
i=%d,data=%v
 7 0xc0000bc000             //同上
i=%d,data=%v
 8 0xc0000bc000             //同上
i=%d,data=%v
 9 0xc0000bc000             //同上

擴(kuò)容容量的選擇遵循以下規(guī)則:

如果原Slice容量小于1024,則新Slice容量將擴(kuò)大為原來的2倍
如果原Slice容量大于等于1024,則新Slice容量將擴(kuò)大為原來的1.25倍

總結(jié)

創(chuàng)建切片時(shí)可根據(jù)實(shí)際需要預(yù)分配容量,盡量避免追加過程中擴(kuò)容操作(append),有利于提升性能

參考

Golang語言slice實(shí)現(xiàn)原理及使用方法

golang slice 切片原理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容