Go slice擴容N連問

fmt.Printf("%p\n", &xxx)的打印問題


后面的參數(shù)必須為 指針類型,否則IDE會有提示,運行后打出來的是%!p(int=0)

最后會到

// fmt0x64 formats a uint64 in hexadecimal and prefixes it with 0x or
// not, as requested, by temporarily setting the sharp flag.
func (p *pp) fmt0x64(v uint64, leading0x bool) {
    sharp := p.fmt.sharp
    p.fmt.sharp = leading0x
    p.fmt.fmtInteger(v, 16, unsigned, 'v', ldigits)
    p.fmt.sharp = sharp
}

https://github.com/golang/go/blob/2a8969cb365a5539b8652d5ac1588aaef78d3e16/src/fmt/print.go#L553

通過查看源碼及試驗可知,fmt.Printf("%p",&sli)得到的是sliceHeader的地址,

想獲取切片底層數(shù)組的地址,要fmt.Printf("%p",&sli[0]),或者fmt.Printf("%p",sli)? (因為sliceheader的第一個字段是底層數(shù)組的pointer)

對任何變量x都可以&x,即這個變量在內(nèi)存里的地址。但如果x本身就是指針類型,fmt.Printf("%p",x)打印的就是這個指針類型對應(yīng)的內(nèi)容,如果fmt.Printf("%p",&x),那就是獲取這個指針類型在內(nèi)存里的地址,結(jié)果也是一個指針類型


// runtime/slice.go下不可導(dǎo)出的
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

// reflect包可以導(dǎo)出的
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}




下面正篇開始


case1: 當(dāng)作為參數(shù)傳遞


共享底層數(shù)組,修改后會影響原值

package main

import (
    "fmt"
)

func main() {

    sli := make([]string, 1)

    sli[0] = "宋江"

    fmt.Println("slice is:", sli) // ["宋江"]

    fmt.Printf("原始sli的長度%d,容量%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 1 1 內(nèi)存地址a=內(nèi)存地址a, 內(nèi)存地址x
    f1(sli)

    fmt.Println("slice is:", sli) // ["晁蓋"]

    fmt.Printf("調(diào)用f1()之后sli的長度%d,容量%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 1 1 內(nèi)存地址a=內(nèi)存地址a,內(nèi)存地址x (因為都是sli這個變量)

    sli666 := sli

    fmt.Printf("sli666的長度%d,容量%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli666), cap(sli666), sli666, &sli666[0], &sli666) // 1 1 內(nèi)存地址a=內(nèi)存地址a,內(nèi)存地址y

}

func f1(sli1 []string) []string {

    sli1[0] = "晁蓋"

    return sli1
}

輸出:

slice is: [宋江]
原始sli的長度1,容量1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000010230=0x14000010230,sliceheader的地址0x1400000c048
slice is: [晁蓋]
調(diào)用f1()之后sli的長度1,容量1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000010230=0x14000010230,sliceheader的地址0x1400000c048
sli666的長度1,容量1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000010230=0x14000010230,sliceheader的地址0x1400000c0c0


再如:

package main

import "fmt"

// append無論如何都是從slice的尾部開始追加數(shù)據(jù); 如果有append操作,很可能會引發(fā)擴容,要特別注意

func main() {

    sli := make([]string, 1)

    sli[0] = "宋江"

    fmt.Printf("[main]原始sli為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:%p=%p,sliceheader的地址%p\n", sli, len(sli), cap(sli), sli, &sli[0], &sli)
    // [main]原始sli為[]string{"宋江"},長度:1,容量:1,內(nèi)存地址a=內(nèi)存地址a,內(nèi)存地址x

    f2(sli)

    fmt.Printf("[main]調(diào)用f2()之后sli為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:%p=%p,sliceheader的地址%p\n", sli, len(sli), cap(sli), sli, &sli[0], &sli)
    // [main]調(diào)用f2()之后sli為[]string{"晁蓋"},長度:1,容量:1,內(nèi)存地址a=內(nèi)存地址a,內(nèi)存地址x

    // 可見,只可能會影響底層數(shù)組的值,**不會影響長度和容量**
}

func f2(sli1 []string) []string {

    fmt.Printf("[f2]f2中append之前sli1的長度%d,容量%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:%p=%p,sliceheader的地址%p\n", len(sli1), cap(sli1), sli1, &sli1[0], &sli1)
    // [f2]f2中append之前sli1的長度1,容量1,內(nèi)存地址a=內(nèi)存地址a,內(nèi)存地址y(可以看出是值傳遞)

    sli1[0] = "晁蓋" // 此時沒有擴容,sli1和main中的sli地址一樣,修改sli1[0]自然會影響main

    sli1 = append(sli1, "盧俊義", "吳用", "公孫勝", "關(guān)勝")

    // 如果將上面的sli1[0] = "晁蓋"去掉,而在下方賦值,此時sli1和main中的sli內(nèi)存地址不同,此時再修改sli1[0]不會影響到main
    //sli1[0] = "晁蓋"

    fmt.Printf("[f2]f2中append之后sli1的長度%d,容量%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:%p=%p,sliceheader的地址%p\n", len(sli1), cap(sli1), sli1, &sli1[0], &sli1)
    // [f2]f2中append之后sli1的長度5,容量5,內(nèi)存地址b=內(nèi)存地址b,內(nèi)存地址y(可以看出是值傳遞)
    return sli1
}

// append一定會改變原始slice的內(nèi)存地址嗎? 不一定,不發(fā)生擴容就不會改變~

輸出:

[main]原始sli為[]string{"宋江"},長度:1,容量:1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:0x14000010230=0x14000010230,sliceheader的地址0x1400000c048
[f2]f2中append之前sli1的長度1,容量1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:0x14000010230=0x14000010230,sliceheader的地址0x1400000c090
[f2]f2中append之后sli1的長度5,容量5,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:0x14000064050=0x14000064050,sliceheader的地址0x1400000c090
[main]調(diào)用f2()之后sli為[]string{"晁蓋"},長度:1,容量:1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:0x14000010230=0x14000010230,sliceheader的地址0x1400000c048


通過索引修改切片元素會影響原切片,但通過append追加元素,則不會(改變原切片的長度和容量)


Go中參數(shù)傳遞都是值傳遞,但當(dāng)參數(shù)為引用類型如slice等時需要注意


package main

import "fmt"

func main() {

    i := make([]int, 10, 12)

    i1 := i[8:]
    //  [0 0] 2 4 地址xxxxxxx
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", i1, len(i1), cap(i1), i1)

    changeSlice(i1)
    // 此時i1變?yōu)榱?[-1 0] 2 4
    // 因為和i底層數(shù)組是一個,所以i也會改變
    fmt.Println(i) // [0 0 0 0 0 0 0 0 -1 0]



    fmt.Println("--------")

    j := make([]int, 10, 12)
    j1 := j[8:]      // [0 0] 2 4
    changeSlice2(j1) // [0 0 10] 3 4 ---為什么不對??

    // [0 0 0 0 0 0 0 0 0 0] 10 12
    fmt.Printf("j: %v, len of j: %d, cap of j: %d\n", j, len(j), cap(j))

    // [0 0 10] 3 4  ---為什么不對??
    fmt.Printf("j1: %v, len of j1: %d, cap of j1: %d\n", j1, len(j1), cap(j1))

}

func changeSlice(s1 []int) {
    s1[0] = -1
}

func changeSlice2(s1 []int) {
    s1 = append(s1, 10)
}

添加一些調(diào)試代碼:

package main

import "fmt"

func main() {

    i := make([]int, 10, 12)

    fmt.Printf("[main] i: %v  len:%d  cap:%d  ptr:%p  sliceheader的地址%p\n", i, len(i), cap(i), i, &i)

    i1 := i[8:]
    fmt.Printf("[main] i1: %v  len:%d  cap:%d  ptr:%p  sliceheader的地址%p\n", i1, len(i1), cap(i1), i1, &i1)

    changeSlice(i1)

    fmt.Printf("[main] i1: %v  len:%d  cap:%d  ptr:%p  sliceheader的地址%p\n", i1, len(i1), cap(i1), i1, &i1)

    fmt.Println(i)

    fmt.Printf("[main] i: %v  len:%d  cap:%d  ptr:%p  sliceheader的地址%p\n", i, len(i), cap(i), i, &i)

    fmt.Println("--------")

    j := make([]int, 10, 12)
    fmt.Printf("[main] j: %v, len of j: %d, cap of j: %d,ptr:%p  sliceheader的地址%p\n", j, len(j), cap(j), j, &j)

    j1 := j[8:]
    fmt.Printf("[main] j1: %v, len of j1: %d, cap of j1: %d, ptr:%p  sliceheader的地址%p\n", j1, len(j1), cap(j1), j1, &j1)

    changeSlice2(j1) // [0 0 10] 3 4 ---為什么不對??
    fmt.Printf("[main] j1: %v, len of j1: %d, cap of j1: %d, ptr:%p  sliceheader的地址%p\n", j1, len(j1), cap(j1), j1, &j1)

    fmt.Println(j)

    fmt.Printf("[main] j: %v  len:%d  cap:%d  ptr:%p  sliceheader的地址%p\n", j, len(j), cap(j), j, &j)

}

func changeSlice(s1 []int) {
    fmt.Printf("[changeSlice] s1: %v  len:%d  cap:%d  ptr:%p  sliceheader的地址%p\n", s1, len(s1), cap(s1), s1, &s1)
    s1[0] = -1
    fmt.Printf("[changeSlice] s1: %v  len:%d  cap:%d  ptr:%p  sliceheader的地址%p\n", s1, len(s1), cap(s1), s1, &s1)
}

func changeSlice2(s1 []int) {
    fmt.Printf("[changeSlice2] s1: %v  len:%d  cap:%d  ptr:%p  sliceheader的地址%p\n", s1, len(s1), cap(s1), s1, &s1)
    s1 = append(s1, 10)
    fmt.Printf("[changeSlice2] s1: %v  len:%d  cap:%d  ptr:%p  sliceheader的地址%p\n", s1, len(s1), cap(s1), s1, &s1)
}

輸出為:

[main] i: [0 0 0 0 0 0 0 0 0 0]  len:10  cap:12  ptr:0x1400010e060  sliceheader的地址0x1400011a030
[main] i1: [0 0]  len:2  cap:4  ptr:0x1400010e0a0  sliceheader的地址0x1400011a078
[changeSlice] s1: [0 0]  len:2  cap:4  ptr:0x1400010e0a0  sliceheader的地址0x1400011a0c0
[changeSlice] s1: [-1 0]  len:2  cap:4  ptr:0x1400010e0a0  sliceheader的地址0x1400011a0c0
[main] i1: [-1 0]  len:2  cap:4  ptr:0x1400010e0a0  sliceheader的地址0x1400011a078
[0 0 0 0 0 0 0 0 -1 0]
[main] i: [0 0 0 0 0 0 0 0 -1 0]  len:10  cap:12  ptr:0x1400010e060  sliceheader的地址0x1400011a030
--------
[main] j: [0 0 0 0 0 0 0 0 0 0], len of j: 10, cap of j: 12,ptr:0x1400010e0c0  sliceheader的地址0x1400011a1b0
[main] j1: [0 0], len of j1: 2, cap of j1: 4, ptr:0x1400010e100  sliceheader的地址0x1400011a1f8
[changeSlice2] s1: [0 0]  len:2  cap:4  ptr:0x1400010e100  sliceheader的地址0x1400011a240
[changeSlice2] s1: [0 10]  len:2  cap:4  ptr:0x1400010e100  sliceheader的地址0x1400011a240
[main] j1: [0 10], len of j1: 2, cap of j1: 4, ptr:0x1400010e100  sliceheader的地址0x1400011a1f8
[0 0 0 0 0 0 0 0 0 10]
[main] j: [0 0 0 0 0 0 0 0 0 10]  len:10  cap:12  ptr:0x1400010e0c0  sliceheader的地址0x1400011a1b0

只能說,通過索引修改切片元素,和通過append追加元素,表現(xiàn)完全不同:

  • 因為append一定至少改變了長度(甚至也改了容量),這種操作只會影響子方法中的,不會影響原值

  • 但如果是修改,子方法修改了某個索引下元素的值,父方法也會受到影響




case2: 擴容


通過 append 操作,可以在 slice 末尾,額外新增一個元素. 需要注意,這里的末尾指的是針對 slice 的長度 len 而言. 這個過程中倘若發(fā)現(xiàn) slice 的剩余容量已經(jīng)不足了,則會對 slice 進行擴容

當(dāng) slice 當(dāng)前的長度 len 與容量 cap 相等時,下一次 append 操作就會引發(fā)一次切片擴容

<font size=3 color="orange">

切片的擴容流程源碼位于 runtime/slice.go 文件的 growslice 方法當(dāng)中,其中核心步驟如下:

? 倘若擴容后預(yù)期的新容量小于原切片的容量,則 panic

? 倘若切片元素大小為 0(元素類型為 struct{}),則直接復(fù)用一個全局的 zerobase 實例,直接返回

? 倘若預(yù)期的新容量超過老容量的兩倍,則直接采用預(yù)期的新容量

? 倘若老容量小于 256,則直接采用老容量的2倍作為新容量

? 倘若老容量已經(jīng)大于等于 256,則在老容量的基礎(chǔ)上擴容 1/4 的比例并且累加上 192 的數(shù)值,持續(xù)這樣處理,直到得到的新容量已經(jīng)大于等于預(yù)期的新容量為止

? 結(jié)合 mallocgc 流程中,對內(nèi)存分配單元 mspan 的等級制度,推算得到實際需要申請的內(nèi)存空間大小

? 調(diào)用 mallocgc,對新切片進行內(nèi)存初始化

? 調(diào)用 memmove 方法,將老切片中的內(nèi)容拷貝到新切片中

? 返回擴容后的新切片

</font>

以上內(nèi)容來自 你真的了解go語言中的切片嗎?


append可能引發(fā)擴容,如果發(fā)生擴容(即cap發(fā)生變化),slice底層數(shù)組的內(nèi)存地址就變了~

package main

import "fmt"

// append一定會改變原始slice底層數(shù)組的內(nèi)存地址嗎。。不一定,沒有發(fā)生擴容就不需要

// https://www.zhihu.com/question/265386326/answer/2321716435

func main() {

    names := make([]int, 3)

    fmt.Printf("切片為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", names, len(names), cap(names), names, &names[0], &names)

    fmt.Println("-------")

    for i := 1; i < 6; i++ {

        fmt.Printf("切片為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", names, len(names), cap(names), names, &names[0], &names)

        names = append(names, i)
    }

    fmt.Println("-------")
    fmt.Printf("切片為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", names, len(names), cap(names), names, &names[0], &names)

}

輸出:

切片為:[]int{0, 0, 0},長度為3,容量為3,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000130000=0x14000130000,sliceheader的地址0x14000114030
-------
切片為:[]int{0, 0, 0},長度為3,容量為3,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000130000=0x14000130000,sliceheader的地址0x14000114030
切片為:[]int{0, 0, 0, 1},長度為4,容量為6,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400012e030=0x1400012e030,sliceheader的地址0x14000114030
切片為:[]int{0, 0, 0, 1, 2},長度為5,容量為6,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400012e030=0x1400012e030,sliceheader的地址0x14000114030
切片為:[]int{0, 0, 0, 1, 2, 3},長度為6,容量為6,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400012e030=0x1400012e030,sliceheader的地址0x14000114030
切片為:[]int{0, 0, 0, 1, 2, 3, 4},長度為7,容量為12,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000102060=0x14000102060,sliceheader的地址0x14000114030
-------
切片為:[]int{0, 0, 0, 1, 2, 3, 4, 5},長度為8,容量為12,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000102060=0x14000102060,sliceheader的地址0x14000114030




case3:由一個數(shù)組得到一個切片,以及兩個切片之間更復(fù)雜的引用


ppackage main

import (
    "fmt"
)

func main() {

    a := [...]int{0, 1, 2, 3}

    fmt.Printf("數(shù)組為:%#v,長度為%d,容量為%d,該數(shù)組的內(nèi)存地址為:%p", a, len(a), cap(a), &a) // [4]int{0 1 2 3}, 4, 4, 地址a
    fmt.Println()

    x := a[:1]
    fmt.Printf("切片x為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", x, len(x), cap(x), x, &x[0], &x) // []int{0}, 1, 4(容量為底層數(shù)組的長度),地址a (也是底層數(shù)組的地址,而不是x這個切片本身的地址)=地址a, 地址x

    y := a[2:]
    fmt.Printf("切片y為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", y, len(y), cap(y), y, &y[0], &y) // []int{2 3}, 2, 2(容量為2?。?!對數(shù)組切一刀留前面的和留后面的對容量來說不一樣), 地址a (同上例)=地址a, 地址y

    x = append(x, y...)
    fmt.Printf("切片x為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", x, len(x), cap(x), x, &x[0], &x) // []int{0 2 3}, 3, 4, 地址a(依然沒有擴容)=地址a, 地址x

    x = append(x, y...)
    fmt.Printf("切片x為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", x, len(x), cap(x), x, &x[0], &x) // ?。?! []int{0 2 3 3 3}, 5, 8 地址b=地址b(因為擴容了,底層數(shù)組就變了),地址x ?。?!

    /*  錯誤!
    
    由上面代碼可知,append(sli1,sli2...),并不等價與sli1 = append(sli1,sli2[0],sli2[1]..,sli2[最后一個元素]),而是類似(無論從最后切片的容量,還是append進去的元素的值)

    for _,ele := range sli2 {
        sli1 = append(sli1,ele)
    }
    錯誤!
    */

    fmt.Println("--------")

    fmt.Println(a, x)
}

輸出:

數(shù)組為:[4]int{0, 1, 2, 3},長度為4,容量為4,該數(shù)組的內(nèi)存地址為:0x140000280e0
切片x為:[]int{0},長度為1,容量為4,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000280e0=0x140000280e0,sliceheader的地址0x1400000c048
切片y為:[]int{2, 3},長度為2,容量為2,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000280f0=0x140000280f0,sliceheader的地址0x1400000c090
切片x為:[]int{0, 2, 3},長度為3,容量為4,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000280e0=0x140000280e0,sliceheader的地址0x1400000c048
切片x為:[]int{0, 2, 3, 3, 3},長度為5,容量為8,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000024500=0x14000024500,sliceheader的地址0x1400000c048
--------
[0 2 3 3] [0 2 3 3 3]

由最后一步的輸出,能否認(rèn)為append(sli1,sli2...),并不等價與sli1 = append(sli1,sli2[0],sli2[1]..,sli2[最后一個元素]),而是類似(無論從最后切片的容量,還是append進去的元素的值)? 即類似

    for _,ele := range sli2 {
        sli1 = append(sli1,ele)
    }

寫demo試一下:

package main

import "fmt"

func main() {

    sli1 := []int{0, 1}
    sli2 := []int{6, 7, 8}

    sli1 = append(sli1, sli2...)

    fmt.Printf("%#v,cap:%d\n", sli1, cap(sli1))

}

輸出: []int{0, 1, 6, 7, 8},cap:6

看起來又是和sli1 = append(sli1,6,7,8)結(jié)果一致的


其實,問題出在第一次x = append(x, y...)這一步

此時x沒有擴容,和y共用一個底層數(shù)組a。 這一步把a改成了 [0 2 3 3],y也因此變成了 [3 3]

所以再第二次x = append(x, y...)前,y就已經(jīng)是 [3 3]了

所以 append(sli1,sli2...),還是等價于append(sli1,sli2[0],sli2[1]..,sli2[最后一個元素])

package main

import "fmt"

func main() {

    a := make([]int, 1, 10)
    b := append(a, 2)
    //c := append(a, 3)

    fmt.Println(a) // [0]
    fmt.Println(b) // [0, 2]

    fmt.Println(a) // 輸出什么?
    //fmt.Println(c)

}

輸出: [0]


package main

import "fmt"

func main() {

    a := make([]int, 1, 10)
    b := append(a, 2)
    c := append(a, 3)

    fmt.Println(a) // [0]
    fmt.Println(b) // 輸出什么?

    fmt.Println(a) // 輸出什么?
    fmt.Println(c) // 輸出什么?

}

輸出: [0 3] ? [0] ? [0 3]


package main

import "fmt"

func main() {

    a := make([]int, 1, 10)

    fmt.Printf("a為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", a, len(a), cap(a), a, &a[0], &a)

    fmt.Println("--------")

    b := append(a, 2)
    fmt.Printf("b為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", b, len(b), cap(b), b, &b[0], &b)

    fmt.Printf("此時a為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", a, len(a), cap(a), a, &a[0], &a)

    fmt.Println("--------")

    c := append(a, 3)
    fmt.Printf("c為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", c, len(c), cap(c), c, &c[0], &c)

    fmt.Printf("最后b為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", b, len(b), cap(b), b, &b[0], &b)

    fmt.Printf("最后a為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", a, len(a), cap(a), a, &a[0], &a)

    fmt.Println(a) // [0]
    fmt.Println(b) // [0, 2]

    fmt.Println(a) // 輸出什么?
    fmt.Println(c) // 輸出什么?

}


輸出:

a為[]int{0},長度:1,容量:10,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098018
--------
b為[]int{0, 2},長度:2,容量:10,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098060
此時a為[]int{0},長度:1,容量:10,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098018
--------
c為[]int{0, 3},長度:2,容量:10,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400009e000=0x1400009e000,sliceheader的地址0x140000980d8
最后b為[]int{0, 3},長度:2,容量:10,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098060
最后a為[]int{0},長度:1,容量:10,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098018
[0]
[0 3]
[0]
[0 3]


package main

import "fmt"

func main() {

    a := make([]int, 1, 1)

    fmt.Printf("a為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", a, len(a), cap(a), a, &a[0], &a)

    fmt.Println("--------")

    b := append(a, 2)
    fmt.Printf("b為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", b, len(b), cap(b), b, &b[0], &b)

    fmt.Printf("此時a為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", a, len(a), cap(a), a, &a[0], &a)

    fmt.Println("--------")

    c := append(a, 3)
    fmt.Printf("c為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", c, len(c), cap(c), c, &c[0], &c)

    fmt.Printf("最后b為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", b, len(b), cap(b), b, &b[0], &b)

    fmt.Printf("最后a為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", a, len(a), cap(a), a, &a[0], &a)

    fmt.Println(a) // [0]
    fmt.Println(b) // [0, 2]

    fmt.Println(a) // 輸出什么?
    fmt.Println(c) // 輸出什么?

}

輸出:

a為[]int{0},長度:1,容量:1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200c8=0x140000200c8,sliceheader的地址0x1400000c048
--------
b為[]int{0, 2},長度:2,容量:2,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200f0=0x140000200f0,sliceheader的地址0x1400000c090
此時a為[]int{0},長度:1,容量:1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200c8=0x140000200c8,sliceheader的地址0x1400000c048
--------
c為[]int{0, 3},長度:2,容量:2,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000020120=0x14000020120,sliceheader的地址0x1400000c108
最后b為[]int{0, 2},長度:2,容量:2,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200f0=0x140000200f0,sliceheader的地址0x1400000c090
最后a為[]int{0},長度:1,容量:1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200c8=0x140000200c8,sliceheader的地址0x1400000c048
[0]
[0 2]
[0]
[0 3]




case4: 一次壓入多個 與 多次壓入一個;元素類型對擴容的影響


為什么不同類型的切片,append之后的len和cap不一樣?


package main

import "fmt"

func main() {
    s1 := []string{"北京", "上海", "深圳"}
    fmt.Printf("len(s1):%d,cap(s1):%d\n", len(s1), cap(s1))
    s1 = append(s1, "廣州", "成都", "重慶", "石家莊", "保定", "邢臺", "張家口", "濟南")
    fmt.Printf("len(s1):%d,cap(s1):%d\n", len(s1), cap(s1))

    fmt.Println("------")

    s2 := []int{1, 2, 3}
    fmt.Printf("len(s2):%d,cap(s2):%d\n", len(s2), cap(s2))
    s2 = append(s2, 4, 5, 6, 7, 8, 9, 10, 11)
    fmt.Printf("len(s2):%d,cap(s2):%d\n", len(s2), cap(s2))
}

輸出:

len(s1):3,cap(s1):3
len(s1):11,cap(s1):11
------
len(s2):3,cap(s2):3
len(s2):11,cap(s2):12


為什么不同類型不一樣?


package main

import (
    "fmt"
    "unsafe"
)

func main() {

    var sli []int64

    // 對于未初始化的slice,使用 &sli[0]會panic
    fmt.Printf("長度:%d 容量:%d 底層數(shù)組的內(nèi)存地址:%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli) // 0,0,底層數(shù)組的內(nèi)存地址:0x0,內(nèi)存地址x

    sli = append(sli, 0)
    fmt.Println(sli) // [0]

    fmt.Printf("長度:%d 容量:%d 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 1,1,內(nèi)存地址b=內(nèi)存地址b(發(fā)生了擴容),內(nèi)存地址x

    fmt.Println(unsafe.Sizeof(sli)) // 24, 其中unsafe.utp指針占8字節(jié),len和cap也都占8個字節(jié)

    sli = append(sli, 1, 2, 3)

    fmt.Println(sli) // [0 1 2 3]

    fmt.Printf("長度:%d 容量:%d 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 4,4,內(nèi)存地址c=內(nèi)存地址c(發(fā)生了擴容),內(nèi)存地址x

    sli = append(sli, 6, 7)

    fmt.Println(sli) // [0 1 2 3 6 7]

    fmt.Printf("長度:%d 容量:%d 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 6,6,內(nèi)存地址d=內(nèi)存地址d(發(fā)生了擴容),內(nèi)存地址x

    sli = append(sli, 8, 9, 10)

    fmt.Println(sli) // [0 1 2 3 6 7 8 9 10]

    fmt.Printf("長度:%d 容量:%d 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 9,16,內(nèi)存地址e=內(nèi)存地址e(發(fā)生了擴容),內(nèi)存地址x

    fmt.Println(unsafe.Sizeof(sli)) // 24, 其中unsafe.utp指針占8字節(jié),len和cap也都占8個字節(jié)

}

輸出:

長度:0 容量:0 底層數(shù)組的內(nèi)存地址:0x0,sliceheader的地址0x1400000c048
[0]
長度:1 容量:1 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200e0=0x140000200e0,sliceheader的地址0x1400000c048
24
[0 1 2 3]
長度:4 容量:4 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000028100=0x14000028100,sliceheader的地址0x1400000c048
[0 1 2 3 6 7]
長度:6 容量:8 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000024500=0x14000024500,sliceheader的地址0x1400000c048
[0 1 2 3 6 7 8 9 10]
長度:9 容量:16 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400001e100=0x1400001e100,sliceheader的地址0x1400000c048
24



package main

import "fmt"

func main() {

    // case1
    m := []int64{2, 3}
    fmt.Println("len of old m is ", len(m)) // 2
    fmt.Println("cap of old m is ", cap(m)) // 2
    fmt.Println("")

    m = append(m, 4, 5, 6)
    fmt.Println("len of m is ", len(m)) //5
    fmt.Println("cap of m is ", cap(m)) //! 6  如果要的容量是原來容量的兩倍還要多, 那新的容量就是所要求的容量大?。?那為何是6而不是5?對于字符串和整型,表現(xiàn)不一樣;而且為何是6?)

    fmt.Println()
    fmt.Println("------")

    // case2
    n := []int64{2, 3}
    fmt.Println("len of old n is ", len(n)) //2
    fmt.Println("cap of old n is ", cap(n)) //2
    fmt.Println("")

    fmt.Printf("切片n為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", n, len(n), cap(n), n, &n[0], &n) // []int64{2, 3}, 2, 2, 地址a=地址a,地址x

    n = append(n, 4)
    fmt.Printf("切片n為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", n, len(n), cap(n), n, &n[0], &n) // []int64{2, 3, 4}, 3, 4(兩倍擴容), 地址b=地址b,地址x

    n = append(n, 5)
    fmt.Printf("切片n為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", n, len(n), cap(n), n, &n[0], &n) // []int64{2, 3, 4, 5}, 4, 4, 地址b=地址b,地址x

    n = append(n, 6)
    fmt.Printf("切片n為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", n, len(n), cap(n), n, &n[0], &n) // []int64{2, 3, 4, 5, 6}, 5, 8(兩倍擴容), 地址c=地址c,地址x

    fmt.Println()

    fmt.Println("len of n is ", len(n)) //5
    fmt.Println("cap of n is ", cap(n)) //! 8  如果要的容量沒有原來容量兩倍大, 那就擴充到原來容量的兩倍.

    fmt.Println("------")

}

fmt.Println("cap of m is ", cap(m)) //! 6 如果要的容量是原來容量的兩倍還要多, 那新的容量就是所要求的容量大???(那為何是6而不是 這一步是為什么?

輸出:

len of old m is  2
cap of old m is  2

len of m is  5
cap of m is  6

------
len of old n is  2
cap of old n is  2

切片n為:[]int64{2, 3},長度為2,容量為2,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200e0=0x140000200e0,sliceheader的地址0x1400000c048
切片n為:[]int64{2, 3, 4},長度為3,容量為4,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000028100=0x14000028100,sliceheader的地址0x1400000c048
切片n為:[]int64{2, 3, 4, 5},長度為4,容量為4,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000028100=0x14000028100,sliceheader的地址0x1400000c048
切片n為:[]int64{2, 3, 4, 5, 6},長度為5,容量為8,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000024500=0x14000024500,sliceheader的地址0x1400000c048

len of n is  5
cap of n is  8
------


再如:

package main

import "fmt"

func main() {
    a := []byte{1, 0}
    fmt.Println("len of old a is ", len(a)) // 2
    fmt.Println("cap of old a is ", cap(a)) // 2
    fmt.Println("")

    a = append(a, 1, 1, 1)
    fmt.Println("len of a is ", len(a)) // 5
    fmt.Println("cap of a is ", cap(a)) // 8

    fmt.Println("------")

    b := []int{23, 51}
    fmt.Println("len of old b is ", len(b)) // 2
    fmt.Println("cap of old b is ", cap(b)) // 2
    fmt.Println("")

    b = append(b, 4, 5, 6)
    fmt.Println("len of b is ", len(b)) // 5
    fmt.Println("cap of b is ", cap(b)) // 6

    fmt.Println("------")

    c := []int32{1, 23}
    fmt.Println("len of old c is ", len(c)) // 2
    fmt.Println("cap of old c is ", cap(c)) // 2
    fmt.Println("")

    c = append(c, 2, 5, 6)
    fmt.Println("len of c is ", len(c)) // 5
    fmt.Println("cap of c is ", cap(c)) // 6

    fmt.Println("------")

    type D struct {
        age  byte
        name string
    }
    d := []D{
        {1, "123"},
        {2, "234"},
    }
    fmt.Println("len of old d is ", len(d)) // 2
    fmt.Println("cap of old d is ", cap(d)) // 2
    fmt.Println("")

    d = append(d, D{4, "456"}, D{5, "567"}, D{6, "678"})
    fmt.Println("len of d is ", len(d)) // 5
    fmt.Println("cap of d is ", cap(d)) // 5

}

再次疑惑: 為什么不同類型的切片,append之后的len和cap不一樣?


package main

import "fmt"

func main() {

    m := []int64{2, 3}
    fmt.Println("len of old m is ", len(m)) // 2
    fmt.Println("cap of old m is ", cap(m)) // 2
    fmt.Println("")

    m = append(m, 4, 5, 6)
    fmt.Println("len of m is ", len(m)) // 5
    fmt.Println("cap of m is ", cap(m)) // 5

    fmt.Println()
    fmt.Println("------")

    n := []int64{2, 3}
    fmt.Println("len of old n is ", len(n)) // 2
    fmt.Println("cap of old n is ", cap(n)) // 2
    fmt.Println("")

    n = append(n, 4)
    n = append(n, 5)
    n = append(n, 6)
    fmt.Println("len of n is ", len(n)) // 5
    fmt.Println("cap of n is ", cap(n)) // 8

    fmt.Println("------")

}

擴容相關(guān)的邏輯go/src/runtime/slice.go中的func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice

但更換版本試了下,和從1.18版本開始的cap策略變更沒關(guān)系

(用1.17和1.21運行,結(jié)果是一樣的)

和element size有關(guān),跟防止overflow以及memory alignment 。ele size 還會影響new cap

不在這里roundup 到tcmalloc的塊大小,其他內(nèi)存也是浪費的。

在此感謝cwx老哥 (https://github.com/cuiweixie)一起研究


https://github.com/golang/go/blob/bdc6ae579aa86d21183c612c8c37916f397afaa8/src/runtime/slice.go#L211-L245

// Specialize for common values of et.Size.
    // For 1 we don't need any division/multiplication.
    // For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
    // For powers of 2, use a variable shift.什么意思

這段注釋解釋了針對常見的 et.Size 值進行特殊處理的原因。

在這段代碼中,et.Size 是一個表示大小的整數(shù)值。注釋中提到了三種常見的情況:

  1. 當(dāng) et.Size 為 1 時,不需要進行除法或乘法運算。這是因為在計算機中,將一個數(shù)左移一位相當(dāng)于乘以 2,右移一位相當(dāng)于除以 2。因此,對于大小為 1 的情況,可以直接使用移位操作來處理,避免了除法或乘法的開銷。

  2. 當(dāng) et.Size 等于當(dāng)前架構(gòu)的指針大?。?code>goarch.PtrSize)時,編譯器會將除法或乘法運算優(yōu)化為一個常數(shù)的位移操作。這是因為指針大小通常是2的冪次方,所以可以通過移位來進行高效的除法或乘法運算。

  3. 對于其他大小為2的冪次方的情況,使用一個可變的位移操作。這意味著將一個數(shù)左移或右移的位數(shù)是可變的,取決于 et.Size 的具體值。這種處理方式仍然利用了位移操作的高效性。

總之,這段注釋是解釋了為什么針對不同的 et.Size 值采取了不同的優(yōu)化策略,以提高計算效率。這些優(yōu)化措施是為了充分利用位移操作和特定的數(shù)學(xué)性質(zhì),從而減少除法或乘法的開銷。


et.Size_不同,影響到最后cap的計算:如果是8字節(jié)的數(shù)據(jù)類型比如int,newcap = int(capmem / goarch.PtrSize); 如果是2的指數(shù)倍的,比如string(占16字節(jié)),newcap = int(capmem >> shift)

et.Size_ 即元素類型占用的內(nèi)存空間,常見的如 int32,存儲大小:4; int64,存儲大小:8; string,存儲大小:16 // string類型底層是一個指針(8字節(jié)),和一個長度字段(8字節(jié))

詳見 利用反射,探究Go語言中的數(shù)據(jù)類型

通過在源碼中添加print,大致捋清了脈絡(luò):

基于1.21版本,switch case有四個優(yōu)先級:

  1. 尺寸為1的(布爾值類型)
  2. 尺寸為8的(64位機器;32位的話為4,在此不考慮)如int64類型;
  3. 尺寸為2的指數(shù)倍的,如string類型
  4. default兜底

最后必然還和內(nèi)存分配有關(guān)系,多級 mheap,mcentral(類似于全局隊列),mcache(類似于本地隊列),mspan(各種尺寸的內(nèi)存各有一塊)

很多個級別,涉及到向下取整,有一部分內(nèi)存碎片


相關(guān)調(diào)試代碼:

func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice {
    oldLen := newLen - num
    if raceenabled {
        callerpc := getcallerpc()
        racereadrangepc(oldPtr, uintptr(oldLen*int(et.Size_)), callerpc, abi.FuncPCABIInternal(growslice))
    }
    if msanenabled {
        msanread(oldPtr, uintptr(oldLen*int(et.Size_)))
    }
    if asanenabled {
        asanread(oldPtr, uintptr(oldLen*int(et.Size_)))
    }

    if newLen < 0 {
        panic(errorString("growslice: len out of range"))
    }

    if et.Size_ == 0 {
        // append should not create a slice with nil pointer but non-zero len.
        // We assume that append doesn't need to preserve oldPtr in this case.
        return slice{unsafe.Pointer(&zerobase), newLen, newLen}
    }

    newcap := oldCap
    doublecap := newcap + newcap
    if newLen > doublecap {
        newcap = newLen
    } else {
        const threshold = 256
        if oldCap < threshold {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            for 0 < newcap && newcap < newLen {
                // Transition from growing 2x for small slices
                // to growing 1.25x for large slices. This formula
                // gives a smooth-ish transition between the two.
                newcap += (newcap + 3*threshold) / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = newLen
            }
        }
    }

    println("爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為:", newcap, "\n")

    println("爽哥調(diào)試-et.Size_值為:", et.Size_, "\n")

    var overflow bool
    var lenmem, newlenmem, capmem uintptr
    // Specialize for common values of et.Size.
    // For 1 we don't need any division/multiplication.
    // For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
    // For powers of 2, use a variable shift.
    switch {
    case et.Size_ == 1:
        println("爽哥調(diào)試-走到了et.Size_ == 1這里\n")
        lenmem = uintptr(oldLen)
        newlenmem = uintptr(newLen)
        capmem = roundupsize(uintptr(newcap))
        println("爽哥調(diào)試-此時capmem值為:", capmem)
        overflow = uintptr(newcap) > maxAlloc
        newcap = int(capmem)
    case et.Size_ == goarch.PtrSize:
        println("爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里\n")
        lenmem = uintptr(oldLen) * goarch.PtrSize
        newlenmem = uintptr(newLen) * goarch.PtrSize
        capmem = roundupsize(uintptr(newcap) * goarch.PtrSize)
        println("爽哥調(diào)試-此時capmem值為:", capmem)
        overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize
        newcap = int(capmem / goarch.PtrSize)
    case isPowerOfTwo(et.Size_):
        println("爽哥調(diào)試-走到了isPowerOfTwo(et.Size_)這里\n")
        var shift uintptr
        if goarch.PtrSize == 8 {
            // Mask shift for better code generation.
            shift = uintptr(sys.TrailingZeros64(uint64(et.Size_))) & 63
        } else {
            shift = uintptr(sys.TrailingZeros32(uint32(et.Size_))) & 31
        }
        lenmem = uintptr(oldLen) << shift
        newlenmem = uintptr(newLen) << shift
        capmem = roundupsize(uintptr(newcap) << shift)
        println("爽哥調(diào)試-此時capmem值為:", capmem)
        println("爽哥調(diào)試-此時shift值為:", shift)
        overflow = uintptr(newcap) > (maxAlloc >> shift)
        newcap = int(capmem >> shift)
        capmem = uintptr(newcap) << shift
    default:
        println("爽哥調(diào)試-走到了default兜底邏輯這里\n")
        lenmem = uintptr(oldLen) * et.Size_
        newlenmem = uintptr(newLen) * et.Size_
        capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap))
        capmem = roundupsize(capmem)
        println("爽哥調(diào)試-此時capmem值為:", capmem)
        newcap = int(capmem / et.Size_)
        capmem = uintptr(newcap) * et.Size_
    }

    println("爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為:", newcap, ", capmem值為:", capmem, "\n")

    // The check of overflow in addition to capmem > maxAlloc is needed
    // to prevent an overflow which can be used to trigger a segfault
    // on 32bit architectures with this example program:
    //
    // type T [1<<27 + 1]int64
    //
    // var d T
    // var s []T
    //
    // func main() {
    //   s = append(s, d, d, d, d)
    //   print(len(s), "\n")
    // }
    if overflow || capmem > maxAlloc {
        panic(errorString("growslice: len out of range"))
    }

    var p unsafe.Pointer
    if et.PtrBytes == 0 {
        p = mallocgc(capmem, nil, false)
        // The append() that calls growslice is going to overwrite from oldLen to newLen.
        // Only clear the part that will not be overwritten.
        // The reflect_growslice() that calls growslice will manually clear
        // the region not cleared here.
        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 lenmem > 0 && writeBarrier.enabled {
            // Only shade the pointers in oldPtr since we know the destination slice p
            // only contains nil pointers because it has been cleared during alloc.
            bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-et.Size_+et.PtrBytes)
        }
    }
    memmove(p, oldPtr, lenmem)

    println("爽哥調(diào)試-最終的的newcap值為:", newcap, ", capmem值為:", capmem, "\n")
    println("爽哥調(diào)試---------------------本輪擴容結(jié)束------------------\n")
    return slice{p, newLen, newcap}
}
package main

import "fmt"

func main() {

    println("~~~~~~~~~~開始進入用戶代碼:~~~~~~~~~~~~~~") // 前面Go底層會有很多調(diào)用到growslice的地方
    s1 := []string{"北京", "上海", "深圳"}

    println("~~~~~~~~~~aaaaaaaaaaaaa:~~~~~~~~~~~~~~")
    fmt.Printf("len(s1):%d,cap(s1):%d\n", len(s1), cap(s1)) // 這步會有調(diào)用growslice的行為
    println("~~~~~~~~~~bbbbbbbbbbbbbb:~~~~~~~~~~~~~~")

    println()
    println("================正式開始:===============")
    s1 = append(s1, "廣州", "成都", "重慶", "石家莊", "保定", "邢臺", "張家口", "濟南")

    println("~~~~~~~~~~cccccccccccccc:~~~~~~~~~~~~~~")
    println("長度為:", len(s1), "容量為:", cap(s1))
    println("~~~~~~~~~~dddddddddddddd:~~~~~~~~~~~~~~")
    // fmt.Printf("len(s1):%d,cap(s1):%d\n", len(s1), cap(s1))

    println("------")

    s2 := []int{1, 2, 3}
    //fmt.Printf("len(s2):%d,cap(s2):%d\n", len(s2), cap(s2))
    println("長度為:", len(s2), "容量為:", cap(s2))

    s2 = append(s2, 4, 5, 6, 7, 8, 9, 10, 11)
    //fmt.Printf("len(s2):%d,cap(s2):%d\n", len(s2), cap(s2))
    println("長度為:", len(s2), "容量為:", cap(s2))
}

輸出:

爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 1 

爽哥調(diào)試-et.Size_值為: 8 

爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里

爽哥調(diào)試-此時capmem值為: 8
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 1 , capmem值為: 8 

爽哥調(diào)試-最終的的newcap值為: 1 , capmem值為: 8 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 1 

爽哥調(diào)試-et.Size_值為: 4 

爽哥調(diào)試-走到了isPowerOfTwo(et.Size_)這里

爽哥調(diào)試-此時capmem值為: 8
爽哥調(diào)試-此時shift值為: 2
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 2 , capmem值為: 8 

爽哥調(diào)試-最終的的newcap值為: 2 , capmem值為: 8 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 4 

爽哥調(diào)試-et.Size_值為: 4 

爽哥調(diào)試-走到了isPowerOfTwo(et.Size_)這里

爽哥調(diào)試-此時capmem值為: 16
爽哥調(diào)試-此時shift值為: 2
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 4 , capmem值為: 16 

爽哥調(diào)試-最終的的newcap值為: 4 , capmem值為: 16 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 1 

爽哥調(diào)試-et.Size_值為: 8 

爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里

爽哥調(diào)試-此時capmem值為: 8
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 1 , capmem值為: 8 

爽哥調(diào)試-最終的的newcap值為: 1 , capmem值為: 8 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 2 

爽哥調(diào)試-et.Size_值為: 8 

爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里

爽哥調(diào)試-此時capmem值為: 16
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 2 , capmem值為: 16 

爽哥調(diào)試-最終的的newcap值為: 2 , capmem值為: 16 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 4 

爽哥調(diào)試-et.Size_值為: 8 

爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里

爽哥調(diào)試-此時capmem值為: 32
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 4 , capmem值為: 32 

爽哥調(diào)試-最終的的newcap值為: 4 , capmem值為: 32 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 82 

爽哥調(diào)試-et.Size_值為: 16 

爽哥調(diào)試-走到了isPowerOfTwo(et.Size_)這里

爽哥調(diào)試-此時capmem值為: 1408
爽哥調(diào)試-此時shift值為: 4
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 88 , capmem值為: 1408 

爽哥調(diào)試-最終的的newcap值為: 88 , capmem值為: 1408 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 8 

爽哥調(diào)試-et.Size_值為: 8 

爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里

爽哥調(diào)試-此時capmem值為: 64
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 8 , capmem值為: 64 

爽哥調(diào)試-最終的的newcap值為: 8 , capmem值為: 64 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 1 

爽哥調(diào)試-et.Size_值為: 16 

爽哥調(diào)試-走到了isPowerOfTwo(et.Size_)這里

爽哥調(diào)試-此時capmem值為: 16
爽哥調(diào)試-此時shift值為: 4
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 1 , capmem值為: 16 

爽哥調(diào)試-最終的的newcap值為: 1 , capmem值為: 16 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

~~~~~~~~~~開始進入用戶代碼:~~~~~~~~~~~~~~
~~~~~~~~~~aaaaaaaaaaaaa:~~~~~~~~~~~~~~
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 1 

爽哥調(diào)試-et.Size_值為: 8 

爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里

爽哥調(diào)試-此時capmem值為: 8
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 1 , capmem值為: 8 

爽哥調(diào)試-最終的的newcap值為: 1 , capmem值為: 8 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 8 

爽哥調(diào)試-et.Size_值為: 1 

爽哥調(diào)試-走到了et.Size_ == 1這里

爽哥調(diào)試-此時capmem值為: 8
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 8 , capmem值為: 8 

爽哥調(diào)試-最終的的newcap值為: 8 , capmem值為: 8 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 16 

爽哥調(diào)試-et.Size_值為: 1 

爽哥調(diào)試-走到了et.Size_ == 1這里

爽哥調(diào)試-此時capmem值為: 16
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 16 , capmem值為: 16 

爽哥調(diào)試-最終的的newcap值為: 16 , capmem值為: 16 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 32 

爽哥調(diào)試-et.Size_值為: 1 

爽哥調(diào)試-走到了et.Size_ == 1這里

爽哥調(diào)試-此時capmem值為: 32
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 32 , capmem值為: 32 

爽哥調(diào)試-最終的的newcap值為: 32 , capmem值為: 32 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

len(s1):3,cap(s1):3
~~~~~~~~~~bbbbbbbbbbbbbb:~~~~~~~~~~~~~~

================正式開始:===============
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 11 

爽哥調(diào)試-et.Size_值為: 16 

爽哥調(diào)試-走到了isPowerOfTwo(et.Size_)這里

爽哥調(diào)試-此時capmem值為: 176
爽哥調(diào)試-此時shift值為: 4
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 11 , capmem值為: 176 

爽哥調(diào)試-最終的的newcap值為: 11 , capmem值為: 176 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

~~~~~~~~~~cccccccccccccc:~~~~~~~~~~~~~~
長度為: 11 容量為: 11
~~~~~~~~~~dddddddddddddd:~~~~~~~~~~~~~~
------
長度為: 3 容量為: 3
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 11 

爽哥調(diào)試-et.Size_值為: 8 

爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里

爽哥調(diào)試-此時capmem值為: 96
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 12 , capmem值為: 96 

爽哥調(diào)試-最終的的newcap值為: 12 , capmem值為: 96 

爽哥調(diào)試---------------------本輪擴容結(jié)束------------------

長度為: 11 容量為: 12


你真的了解go語言中的切片嗎? 最后 3.12 問題12差不多




case5: 初始容量的確定


  • 通過s := make([]int,10)這種方式,如果沒有指定cap的值,則默認(rèn)與len相同

  • 也可以顯式指定,可以很大,但不能比len小,否則會報len larger than cap in make([]int)


package main

import "fmt"

func main() {

    demo := make([]int, 9)

    demo2 := demo

    // []int{0,0,0,0,0,0,0,0,0}, 9, 9(而不是16!), 地址a, 地址x
    fmt.Printf("切片demo為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p,sliceheader的地址為%p\n", demo, len(demo), cap(demo), demo, &demo)

    //  []int{0,0,0,0,0,0,0,0,0}, 9, 9, 地址a, 地址y
    fmt.Printf("切片demo2為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p,sliceheader的地址為%p\n", demo2, len(demo2), cap(demo2), demo2, &demo2)

    fmt.Println("-------")

    demo3 := append(demo, 1)

    //  []int{0,0,0,0,0,0,0,0,0, 1}, 10, 18(為什么?!), 地址b(發(fā)生了擴容), 地址z
    fmt.Printf("切片demo3為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p,sliceheader的地址為%p\n", demo3, len(demo3), cap(demo3), demo3, &demo3)

    demo4 := append(demo3, 1, 2, 3)

    //  []int{0,0,0,0,0,0,0,0,0, 1,1,2,3}, 13, 18, 地址b(未擴容), 地址u
    fmt.Printf("切片demo4為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p,sliceheader的地址為%p\n", demo4, len(demo4), cap(demo4), demo4, &demo4)

    fmt.Println()

}

輸出:

切片demo為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0},長度為9,容量為9,底層數(shù)組的內(nèi)存地址為0x140000260f0,sliceheader的地址為0x1400000c048
切片demo2為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0},長度為9,容量為9,底層數(shù)組的內(nèi)存地址為0x140000260f0,sliceheader的地址為0x1400000c060
-------
切片demo3為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 1},長度為10,容量為18,底層數(shù)組的內(nèi)存地址為0x14000102000,sliceheader的地址為0x1400000c0d8
切片demo4為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3},長度為13,容量為18,底層數(shù)組的內(nèi)存地址為0x14000102000,sliceheader的地址為0x1400000c120

為什么初始容量為9?

為什么后面擴容是18而不是16?

通過 append 操作,可以在 slice 末尾,額外新增一個元素. 需要注意,這里的末尾指的是針對 slice 的長度 len 而言. 這個過程中倘若發(fā)現(xiàn) slice 的剩余容量已經(jīng)不足了,則會對 slice 進行擴容

當(dāng) slice 當(dāng)前的長度 len 與容量 cap 相等時,下一次 append 操作就會引發(fā)一次切片擴容

<font size=3 color="orange">

切片的擴容流程源碼位于 runtime/slice.go 文件的 growslice 方法當(dāng)中,其中核心步驟如下:

? 倘若擴容后預(yù)期的新容量小于原切片的容量,則 panic

? 倘若切片元素大小為 0(元素類型為 struct{}),則直接復(fù)用一個全局的 zerobase 實例,直接返回

? 倘若預(yù)期的新容量超過老容量的兩倍,則直接采用預(yù)期的新容量

? 倘若老容量小于 256,則直接采用老容量的2倍作為新容量

? 倘若老容量已經(jīng)大于等于 256,則在老容量的基礎(chǔ)上擴容 1/4 的比例并且累加上 192 的數(shù)值,持續(xù)這樣處理,直到得到的新容量已經(jīng)大于等于預(yù)期的新容量為止

? 結(jié)合 mallocgc 流程中,對內(nèi)存分配單元 mspan 的等級制度,推算得到實際需要申請的內(nèi)存空間大小

? 調(diào)用 mallocgc,對新切片進行內(nèi)存初始化

? 調(diào)用 memmove 方法,將老切片中的內(nèi)容拷貝到新切片中

? 返回擴容后的新切片

</font>

runtime/slice.go:

    newcap := oldCap
    doublecap := newcap + newcap
    if newLen > doublecap {
        newcap = newLen
    } else {
        const threshold = 256
        if oldCap < threshold {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            for 0 < newcap && newcap < newLen {
                // Transition from growing 2x for small slices
                // to growing 1.25x for large slices. This formula
                // gives a smooth-ish transition between the two.
                newcap += (newcap + 3*threshold) / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = newLen
            }
        }
    }

newcap 經(jīng)過如上邏輯后,還要再根據(jù)元素類型,做一次處理。詳見case4中的源碼調(diào)試

package main

import "fmt"

//https://dashen.tech/2010/03/02/golang%E4%B9%8Bslice%E4%B8%AD%E7%9A%84%E5%B0%8Ftips/
// https://dashen.tech/2020/08/05/%E4%B8%A4%E4%B8%AAgolang%E5%B0%8F%E9%97%AE%E9%A2%98/
//https://dashen.tech/2021/03/01/%E4%B8%80%E4%B8%8D%E7%95%99%E7%A5%9E%E5%B0%B1%E6%8E%89%E5%9D%91/#map%E5%92%8Cslice%E5%8F%98%E9%87%8F%E7%9A%84%E8%B5%8B%E5%80%BC%E4%BD%9C%E7%94%A8%E8%8C%83%E5%9B%B4%E9%97%AE%E9%A2%98

// 特別留意append

func main() {

    demo := make([]int, 10)

    demo2 := demo

    // []int{0,0,0,0,0,0,0,0,0,0}, 10, 10(而不是16?。? 地址a
    fmt.Printf("切片demo為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", demo, len(demo), cap(demo), demo)

    // []int{0,0,0,0,0,0,0,0,0,0}, 10, 10(而不是16!), 地址a
    fmt.Printf("切片demo2為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", demo2, len(demo2), cap(demo2), demo2)

    demo3 := append(demo, 1)
    // []int{0,0,0,0,0,0,0,0,0,0,1}, 11, 20?(而不是16?。? 地址b(發(fā)生了擴容)
    fmt.Printf("切片demo3為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", demo3, len(demo3), cap(demo3), demo3)

    demo4 := append(demo3, 1, 2, 3)

    // []int{0,0,0,0,0,0,0,0,0,0,1,1,2,3}, 14, 20(而不是16?。? 地址b(未發(fā)生擴容)
    fmt.Printf("切片demo4為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", demo4, len(demo4), cap(demo4), demo4)

    fmt.Println()

    fmt.Println("---------------------")

    // 對于這種sli2 = append(sli1,6,6,6),如果沒發(fā)生擴容,sli1和sli2底層數(shù)組一樣

    // 對于sli1 = append(sli,6,6), sli2 = append(sli,8,8),即便容量一樣,sli1和sli2底層數(shù)組也不一樣..

    //var sli []int
    //sli := make([]int, 0)
    sli := make([]int, 11)

    // []int{0,0,0,0,0,0,0,0,0,0,0}, 11, 11(而不是16?。? 地址c
    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    sli1 := append(sli, 1)

    // []int{0,0,0,0,0,0,0,0,0,0,0,1}, 12, 22(而不是16!), 地址d(發(fā)生了擴容)
    fmt.Printf("[sli1] %v  len:%d  cap:%d  ptr:%p\n", sli1, len(sli1), cap(sli1), sli1)

    fmt.Println()

    // []int{0,0,0,0,0,0,0,0,0,0,0}, 11, 11(而不是16?。? 地址c
    fmt.Printf("此時切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    // []int{0,0,0,0,0,0,0,0,0,0,0,1,2}, 13, 22(而不是26!), 地址e(發(fā)生了擴容)
    sli2 := append(sli, 1, 2)
    fmt.Printf("[sli2] %v  len:%d  cap:%d  ptr:%p\n", sli2, len(sli2), cap(sli2), sli2)

    fmt.Println()

    // []int{0,0,0,0,0,0,0,0,0,0,0}, 11, 11(而不是16?。? 地址c
    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    sli3 := append(sli, 1, 2, 3)
    // []int{0,0,0,0,0,0,0,0,0,0,0,1,2,3}, 14, 22(而不是28!), 地址f(發(fā)生了擴容)
    fmt.Printf("[sli3] %v  len:%d  cap:%d  ptr:%p\n", sli3, len(sli3), cap(sli3), sli3)
    fmt.Println()

    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    sli4 := append(sli, 1, 2, 3, 4)
    fmt.Printf("[sli4] %v  len:%d  cap:%d  ptr:%p\n", sli4, len(sli4), cap(sli4), sli4)

    fmt.Println()

    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    sli5 := append(sli, 1, 2, 3, 4, 5)
    fmt.Printf("[sli5] %v  len:%d  cap:%d  ptr:%p\n", sli5, len(sli5), cap(sli5), sli5)
    fmt.Println()

    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    sli6 := append(sli, 1, 2, 3, 4, 5, 6)
    fmt.Printf("[sli6] %v  len:%d  cap:%d  ptr:%p\n", sli6, len(sli6), cap(sli6), sli6)

    fmt.Println()
    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    sli7 := append(sli, 1, 2, 3, 4, 5, 6, 7)
    fmt.Printf("[sli7] %v  len:%d  cap:%d  ptr:%p\n", sli7, len(sli7), cap(sli7), sli7)
    fmt.Println()

    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    sli8 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8)
    fmt.Printf("[sli8] %v  len:%d  cap:%d  ptr:%p\n", sli8, len(sli8), cap(sli8), sli8)
    fmt.Println()

    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    sli9 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    fmt.Printf("[sli9] %v  len:%d  cap:%d  ptr:%p\n", sli9, len(sli9), cap(sli9), sli9)
    fmt.Println()

    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    sli10 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    fmt.Printf("[sli10] %v  len:%d  cap:%d  ptr:%p\n", sli10, len(sli10), cap(sli10), sli10)
    fmt.Println()

    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    sli11 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
    fmt.Printf("[sli11] %v  len:%d  cap:%d  ptr:%p\n", sli11, len(sli11), cap(sli11), sli11)
    fmt.Println()

    // []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,地址c
    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    sli12 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
    // [sli12] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 10 11 12]  len:23  cap:24  地址t
    fmt.Printf("[sli12] %v  len:%d  cap:%d  ptr:%p\n", sli12, len(sli12), cap(sli12), sli12)
    fmt.Println()

    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

    sli13 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
    fmt.Printf("[sli13] %v  len:%d  cap:%d  ptr:%p\n", sli13, len(sli13), cap(sli13), sli13)
    fmt.Println()

    fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)

}

輸出:

切片demo為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為10,容量為10,底層數(shù)組的內(nèi)存地址為0x140000ba000
切片demo2為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為10,容量為10,底層數(shù)組的內(nèi)存地址為0x140000ba000
切片demo3為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},長度為11,容量為20,底層數(shù)組的內(nèi)存地址為0x140000c2000
切片demo4為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3},長度為14,容量為20,底層數(shù)組的內(nèi)存地址為0x140000c2000

---------------------
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli1] [0 0 0 0 0 0 0 0 0 0 0 1]  len:12  cap:22  ptr:0x140000c6000

此時切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli2] [0 0 0 0 0 0 0 0 0 0 0 1 2]  len:13  cap:22  ptr:0x140000c60b0

切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli3] [0 0 0 0 0 0 0 0 0 0 0 1 2 3]  len:14  cap:22  ptr:0x140000c6160

切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli4] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4]  len:15  cap:22  ptr:0x140000c6210

切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli5] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5]  len:16  cap:22  ptr:0x140000c62c0

切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli6] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6]  len:17  cap:22  ptr:0x140000c6370

切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli7] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7]  len:18  cap:22  ptr:0x140000c6420

切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli8] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8]  len:19  cap:22  ptr:0x140000c64d0

切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli9] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]  len:20  cap:22  ptr:0x140000c6580

切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli10] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 10]  len:21  cap:22  ptr:0x140000c6630

切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli11] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 10 11]  len:22  cap:22  ptr:0x140000c66e0

切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli12] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 10 11 12]  len:23  cap:24  ptr:0x140000c8000

切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli13] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 10 11 12 13]  len:24  cap:24  ptr:0x140000c80c0

切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060


來自 golang之slice中的小tips

package main

import "fmt"

func main() {

    var sli []int

    sli1 := append(sli, 1)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli1, len(sli1), cap(sli1), sli1)

    sli2 := append(sli, 1, 2)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli2, len(sli2), cap(sli2), sli2)

    sli3 := append(sli, 1, 2, 3)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli3, len(sli3), cap(sli3), sli3)

    sli4 := append(sli, 1, 2, 3, 4)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli4, len(sli4), cap(sli4), sli4)

    sli5 := append(sli, 1, 2, 3, 4, 5)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli5, len(sli5), cap(sli5), sli5)

    sli6 := append(sli, 1, 2, 3, 4, 5, 6)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli6, len(sli6), cap(sli6), sli6)

    sli7 := append(sli, 1, 2, 3, 4, 5, 6, 7)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli7, len(sli7), cap(sli7), sli7)

    sli8 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli8, len(sli8), cap(sli8), sli8)

    sli9 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli9, len(sli9), cap(sli9), sli9)

    sli10 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli10, len(sli10), cap(sli10), sli10)

    sli11 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli11, len(sli11), cap(sli11), sli11)

    sli12 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli12, len(sli12), cap(sli12), sli12)

    sli13 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
    fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", sli13, len(sli13), cap(sli13), sli13)

}

輸出為:

[1]  len:1  cap:1  ptr:0x140000200c8
[1 2]  len:2  cap:2  ptr:0x140000200f0
[1 2 3]  len:3  cap:3  ptr:0x1400001c0d8
[1 2 3 4]  len:4  cap:4  ptr:0x14000028100
[1 2 3 4 5]  len:5  cap:6  ptr:0x14000022270
[1 2 3 4 5 6]  len:6  cap:6  ptr:0x140000222a0
[1 2 3 4 5 6 7]  len:7  cap:8  ptr:0x14000024500
[1 2 3 4 5 6 7 8]  len:8  cap:8  ptr:0x14000024540
[1 2 3 4 5 6 7 8 9]  len:9  cap:10  ptr:0x140000260f0
[1 2 3 4 5 6 7 8 9 10]  len:10  cap:10  ptr:0x14000026140
[1 2 3 4 5 6 7 8 9 10 11]  len:11  cap:12  ptr:0x140000165a0
[1 2 3 4 5 6 7 8 9 10 11 12]  len:12  cap:12  ptr:0x14000016600
[1 2 3 4 5 6 7 8 9 10 11 12 13]  len:13  cap:14  ptr:0x1400001a230


兩種不同的聲明方式,對初始容量的影響


(圖片來自網(wǎng)絡(luò))

package main

import "fmt"

func create(iterations int) []int {
    a := make([]int, 0)
    for i := 0; i < iterations; i++ {
        a = append(a, i)
    }
    return a
}

func main() {
    sliceFromLoop()

    fmt.Println("-----------------------")

    sliceFromLiteral()

}

func sliceFromLoop() {
    fmt.Printf("** NOT working as expected: **\n\n")
    i := create(11)
    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},11,11(為什么??),地址b
    fmt.Printf("切片i為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)
    fmt.Println("initial slice: ", i)

    fmt.Println()

    j := append(i, 100)
    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100},12,22,地址c(發(fā)生了擴容)
    fmt.Printf("切片j為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", j, len(j), cap(j), j)
    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},11,11,地址b
    fmt.Printf("【原始切片i】為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)

    fmt.Println()

    g := append(i, 101)
    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 101},12,22,地址d(發(fā)生了擴容)
    fmt.Printf("切片g為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", g, len(g), cap(g), g)
    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},11,11,地址b
    fmt.Printf("【原始切片i】為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)

    fmt.Println()

    h := append(i, 102)

    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 102},12,22,地址f(發(fā)生了擴容)
    fmt.Printf("切片h為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", h, len(h), cap(h), h)
    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},11,11,地址b
    fmt.Printf("【原始切片i】為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)

    fmt.Println()

    fmt.Println("最后的結(jié)果:")
    fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h) // 因為發(fā)生了擴容,j, g, h之間不會相互影響
}

func sliceFromLiteral() {
    fmt.Printf("\n\n** working as expected: **\n")
    i := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 11, 16, 地址a
    fmt.Printf("切片i為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)
    fmt.Println("initial slice: ", i)

    fmt.Println()

    j := append(i, 100)
    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100},12,16,地址a
    fmt.Printf("切片j為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", j, len(j), cap(j), j)

    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 11, 16, 地址a
    fmt.Printf("【原始切片i】為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)
    fmt.Println()

    g := append(i, 101)
    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 101}, 12, 16, 地址a
    fmt.Printf("切片g為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", g, len(g), cap(g), g)
    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 11, 16, 地址a
    fmt.Printf("【原始切片i】為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)

    fmt.Println()

    h := append(i, 102)
    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 102}, 12, 16, 地址a
    fmt.Printf("切片h為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", h, len(h), cap(h), h)
    // []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 11, 16, 地址a
    fmt.Printf("【原始切片i】為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)

    fmt.Println()

    fmt.Println("最后的結(jié)果:")
    fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h) // i, j, g, h共用一個底層數(shù)組,改值 會相互影響
}




番外: 與append無關(guān)的一些case:



迭代過程中修改切片的值


package main

func main() {
    var s = []int{1, 2, 3}

    for i, n := range s {
        if i == 0 {
            s[1], s[2] = 8, 9
        }
        print(n)
    }
}

輸出: 189


并發(fā)寫入


package main

import (
    "fmt"
)

func main() {

    var sli []int

    for i := 0; i < 10; i++ {

        go func() {
            for j := 0; j < 10; j++ {
                sli = append(sli, 1)
            }
        }()
    }

    //time.Sleep(time.Microsecond) // 不加這一行,很可能是0或較小的數(shù);加上這行,也小于100
    fmt.Println(len(sli))

}

加鎖后:

package main

import (
    "fmt"
    "sync"
    "time"
)

var mu sync.Mutex

func main() {

    var sli []int

    for i := 0; i < 10; i++ {

        go func() {
            for j := 0; j < 10; j++ {
                mu.Lock()
                sli = append(sli, 1)
                mu.Unlock()
            }
        }()
    }

    time.Sleep(time.Microsecond)

    time.Sleep(5e9) // 不加這一行,結(jié)果一般小于100;100次加鎖解鎖操作,1ms內(nèi)完不成
    fmt.Println(len(sli))

}
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {

    var sli []int
    var mu sync.Mutex
    for i := 0; i < 10; i++ {
        //var mu sync.Mutex
        go func() {
            // var mu sync.Mutex
            for j := 0; j < 10; j++ {
                // var mu sync.Mutex
                mu.Lock()
                sli = append(sli, 1)
                mu.Unlock()
            }
        }()
    }

    time.Sleep(time.Microsecond)

    time.Sleep(5e9) // 不加這一行,結(jié)果一般小于100;100次加鎖解鎖操作,1ms內(nèi)完不成
    fmt.Println(len(sli))

}

把鎖初始化的操作放在循環(huán)內(nèi)是不行的,最后的結(jié)果一定小于100.

要放到全局,或者循環(huán)體外,只初始化一把鎖,而不是n把


interface 類型的切片可能出錯的點


package main

import (
    "fmt"
)

func main() {
    sli := []int64{1, 2, 3}
    var sliIface []interface{}

    for _, item := range sli {
        sliIface = append(sliIface, item)
    }

    rs := InSliceIface(int64(2), sliIface) // 2 必須指定為int64類型,否則會當(dāng)成int,最終結(jié)果為false
    fmt.Println(rs)
}



func InSliceIface(ele interface{}, sli []interface{}) bool {
    for _, v := range sli {
        if v == ele {
            return true
        }
    }
    return false
}


泛型切片


package main

import (
    "fmt"
)

func main() {
    sli := []float64{1, 2, 3.14}

    rs := InSlice(3.14, sli)
    fmt.Println(rs)

}

func InSlice[T int | int8 | int32 | int64 | float32 | float64 | string](ele T, sli []T) bool {
    for _, v := range sli {
        if v == ele {
            return true
        }
    }
    return false
}

本文由mdnice多平臺發(fā)布

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

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

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