美文网首页
Go slice扩容N连问

Go slice扩容N连问

作者: cuishuang | 来源:发表于2023-06-30 07:22 被阅读0次

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

    后面的参数必须为 指针类型,否则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的地址,

    想获取切片底层数组的地址,要fmt.Printf("%p",&sli[0]),或者fmt.Printf("%p",sli)? (因为sliceheader的第一个字段是底层数组的pointer)

    对任何变量x都可以&x,即这个变量在内存里的地址。但如果x本身就是指针类型,fmt.Printf("%p",x)打印的就是这个指针类型对应的内容,如果fmt.Printf("%p",&x),那就是获取这个指针类型在内存里的地址,结果也是一个指针类型

    
    // runtime/slice.go下不可导出的
    type slice struct {
        array unsafe.Pointer
        len   int
        cap   int
    }
    
    // reflect包可以导出的
    type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
    }
    

    下面正篇开始

    case1: 当作为参数传递

    共享底层数组,修改后会影响原值

    package main
    
    import (
        "fmt"
    )
    
    func main() {
    
        sli := make([]string, 1)
    
        sli[0] = "宋江"
    
        fmt.Println("slice is:", sli) // ["宋江"]
    
        fmt.Printf("原始sli的长度%d,容量%d,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 1 1 内存地址a=内存地址a, 内存地址x
        f1(sli)
    
        fmt.Println("slice is:", sli) // ["晁盖"]
    
        fmt.Printf("调用f1()之后sli的长度%d,容量%d,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 1 1 内存地址a=内存地址a,内存地址x (因为都是sli这个变量)
    
        sli666 := sli
    
        fmt.Printf("sli666的长度%d,容量%d,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", len(sli666), cap(sli666), sli666, &sli666[0], &sli666) // 1 1 内存地址a=内存地址a,内存地址y
    
    }
    
    func f1(sli1 []string) []string {
    
        sli1[0] = "晁盖"
    
        return sli1
    }
    

    输出:

    slice is: [宋江]
    原始sli的长度1,容量1,底层数组的内存地址的两种表示方式应该一致0x14000010230=0x14000010230,sliceheader的地址0x1400000c048
    slice is: [晁盖]
    调用f1()之后sli的长度1,容量1,底层数组的内存地址的两种表示方式应该一致0x14000010230=0x14000010230,sliceheader的地址0x1400000c048
    sli666的长度1,容量1,底层数组的内存地址的两种表示方式应该一致0x14000010230=0x14000010230,sliceheader的地址0x1400000c0c0
    

    再如:

    package main
    
    import "fmt"
    
    // append无论如何都是从slice的尾部开始追加数据; 如果有append操作,很可能会引发扩容,要特别注意
    
    func main() {
    
        sli := make([]string, 1)
    
        sli[0] = "宋江"
    
        fmt.Printf("[main]原始sli为%#v,长度:%d,容量:%d,底层数组的内存地址的两种表示方式应该一致:%p=%p,sliceheader的地址%p\n", sli, len(sli), cap(sli), sli, &sli[0], &sli)
        // [main]原始sli为[]string{"宋江"},长度:1,容量:1,内存地址a=内存地址a,内存地址x
    
        f2(sli)
    
        fmt.Printf("[main]调用f2()之后sli为%#v,长度:%d,容量:%d,底层数组的内存地址的两种表示方式应该一致:%p=%p,sliceheader的地址%p\n", sli, len(sli), cap(sli), sli, &sli[0], &sli)
        // [main]调用f2()之后sli为[]string{"晁盖"},长度:1,容量:1,内存地址a=内存地址a,内存地址x
    
        // 可见,只可能会影响底层数组的值,**不会影响长度和容量**
    }
    
    func f2(sli1 []string) []string {
    
        fmt.Printf("[f2]f2中append之前sli1的长度%d,容量%d,底层数组的内存地址的两种表示方式应该一致:%p=%p,sliceheader的地址%p\n", len(sli1), cap(sli1), sli1, &sli1[0], &sli1)
        // [f2]f2中append之前sli1的长度1,容量1,内存地址a=内存地址a,内存地址y(可以看出是值传递)
    
        sli1[0] = "晁盖" // 此时没有扩容,sli1和main中的sli地址一样,修改sli1[0]自然会影响main
    
        sli1 = append(sli1, "卢俊义", "吴用", "公孙胜", "关胜")
    
        // 如果将上面的sli1[0] = "晁盖"去掉,而在下方赋值,此时sli1和main中的sli内存地址不同,此时再修改sli1[0]不会影响到main
        //sli1[0] = "晁盖"
    
        fmt.Printf("[f2]f2中append之后sli1的长度%d,容量%d,底层数组的内存地址的两种表示方式应该一致:%p=%p,sliceheader的地址%p\n", len(sli1), cap(sli1), sli1, &sli1[0], &sli1)
        // [f2]f2中append之后sli1的长度5,容量5,内存地址b=内存地址b,内存地址y(可以看出是值传递)
        return sli1
    }
    
    // append一定会改变原始slice的内存地址吗? 不一定,不发生扩容就不会改变~
    

    输出:

    [main]原始sli为[]string{"宋江"},长度:1,容量:1,底层数组的内存地址的两种表示方式应该一致:0x14000010230=0x14000010230,sliceheader的地址0x1400000c048
    [f2]f2中append之前sli1的长度1,容量1,底层数组的内存地址的两种表示方式应该一致:0x14000010230=0x14000010230,sliceheader的地址0x1400000c090
    [f2]f2中append之后sli1的长度5,容量5,底层数组的内存地址的两种表示方式应该一致:0x14000064050=0x14000064050,sliceheader的地址0x1400000c090
    [main]调用f2()之后sli为[]string{"晁盖"},长度:1,容量:1,底层数组的内存地址的两种表示方式应该一致:0x14000010230=0x14000010230,sliceheader的地址0x1400000c048
    

    通过索引修改切片元素会影响原切片,但通过append追加元素,则不会(改变原切片的长度和容量)

    Go中参数传递都是值传递,但当参数为引用类型如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变为了 [-1 0] 2 4
        // 因为和i底层数组是一个,所以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)
    }
    

    添加一些调试代码:

    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追加元素,表现完全不同:

    • 因为append一定至少改变了长度(甚至也改了容量),这种操作只会影响子方法中的,不会影响原值

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


    case2: 扩容

    通过 append 操作,可以在 slice 末尾,额外新增一个元素. 需要注意,这里的末尾指的是针对 slice 的长度 len 而言. 这个过程中倘若发现 slice 的剩余容量已经不足了,则会对 slice 进行扩容

    当 slice 当前的长度 len 与容量 cap 相等时,下一次 append 操作就会引发一次切片扩容

    <font size=3 color="orange">

    切片的扩容流程源码位于 runtime/slice.go 文件的 growslice 方法当中,其中核心步骤如下:

    • 倘若扩容后预期的新容量小于原切片的容量,则 panic

    • 倘若切片元素大小为 0(元素类型为 struct{}),则直接复用一个全局的 zerobase 实例,直接返回

    • 倘若预期的新容量超过老容量的两倍,则直接采用预期的新容量

    • 倘若老容量小于 256,则直接采用老容量的2倍作为新容量

    • 倘若老容量已经大于等于 256,则在老容量的基础上扩容 1/4 的比例并且累加上 192 的数值,持续这样处理,直到得到的新容量已经大于等于预期的新容量为止

    • 结合 mallocgc 流程中,对内存分配单元 mspan 的等级制度,推算得到实际需要申请的内存空间大小

    • 调用 mallocgc,对新切片进行内存初始化

    • 调用 memmove 方法,将老切片中的内容拷贝到新切片中

    • 返回扩容后的新切片

    </font>

    以上内容来自 你真的了解go语言中的切片吗?

    append可能引发扩容,如果发生扩容(即cap发生变化),slice底层数组的内存地址就变了~

    package main
    
    import "fmt"
    
    // append一定会改变原始slice底层数组的内存地址吗。。不一定,没有发生扩容就不需要
    
    // https://www.zhihu.com/question/265386326/answer/2321716435
    
    func main() {
    
        names := make([]int, 3)
    
        fmt.Printf("切片为:%#v,长度为%d,容量为%d,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", names, len(names), cap(names), names, &names[0], &names)
    
    }
    

    输出:

    切片为:[]int{0, 0, 0},长度为3,容量为3,底层数组的内存地址的两种表示方式应该一致0x14000130000=0x14000130000,sliceheader的地址0x14000114030
    -------
    切片为:[]int{0, 0, 0},长度为3,容量为3,底层数组的内存地址的两种表示方式应该一致0x14000130000=0x14000130000,sliceheader的地址0x14000114030
    切片为:[]int{0, 0, 0, 1},长度为4,容量为6,底层数组的内存地址的两种表示方式应该一致0x1400012e030=0x1400012e030,sliceheader的地址0x14000114030
    切片为:[]int{0, 0, 0, 1, 2},长度为5,容量为6,底层数组的内存地址的两种表示方式应该一致0x1400012e030=0x1400012e030,sliceheader的地址0x14000114030
    切片为:[]int{0, 0, 0, 1, 2, 3},长度为6,容量为6,底层数组的内存地址的两种表示方式应该一致0x1400012e030=0x1400012e030,sliceheader的地址0x14000114030
    切片为:[]int{0, 0, 0, 1, 2, 3, 4},长度为7,容量为12,底层数组的内存地址的两种表示方式应该一致0x14000102060=0x14000102060,sliceheader的地址0x14000114030
    -------
    切片为:[]int{0, 0, 0, 1, 2, 3, 4, 5},长度为8,容量为12,底层数组的内存地址的两种表示方式应该一致0x14000102060=0x14000102060,sliceheader的地址0x14000114030
    

    case3:由一个数组得到一个切片,以及两个切片之间更复杂的引用

    ppackage main
    
    import (
        "fmt"
    )
    
    func main() {
    
        a := [...]int{0, 1, 2, 3}
    
        fmt.Printf("数组为:%#v,长度为%d,容量为%d,该数组的内存地址为:%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,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", x, len(x), cap(x), x, &x[0], &x) // []int{0}, 1, 4(容量为底层数组的长度),地址a (也是底层数组的地址,而不是x这个切片本身的地址)=地址a, 地址x
    
        y := a[2:]
        fmt.Printf("切片y为:%#v,长度为%d,容量为%d,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", y, len(y), cap(y), y, &y[0], &y) // []int{2 3}, 2, 2(容量为2!!!对数组切一刀留前面的和留后面的对容量来说不一样), 地址a (同上例)=地址a, 地址y
    
        x = append(x, y...)
        fmt.Printf("切片x为:%#v,长度为%d,容量为%d,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", x, len(x), cap(x), x, &x[0], &x) // !!! []int{0 2 3 3 3}, 5, 8 地址b=地址b(因为扩容了,底层数组就变了),地址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)
    }
    

    输出:

    数组为:[4]int{0, 1, 2, 3},长度为4,容量为4,该数组的内存地址为:0x140000280e0
    切片x为:[]int{0},长度为1,容量为4,底层数组的内存地址的两种表示方式应该一致0x140000280e0=0x140000280e0,sliceheader的地址0x1400000c048
    切片y为:[]int{2, 3},长度为2,容量为2,底层数组的内存地址的两种表示方式应该一致0x140000280f0=0x140000280f0,sliceheader的地址0x1400000c090
    切片x为:[]int{0, 2, 3},长度为3,容量为4,底层数组的内存地址的两种表示方式应该一致0x140000280e0=0x140000280e0,sliceheader的地址0x1400000c048
    切片x为:[]int{0, 2, 3, 3, 3},长度为5,容量为8,底层数组的内存地址的两种表示方式应该一致0x14000024500=0x14000024500,sliceheader的地址0x1400000c048
    --------
    [0 2 3 3] [0 2 3 3 3]
    

    由最后一步的输出,能否认为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)结果一致的

    其实,问题出在第一次x = append(x, y...)这一步

    此时x没有扩容,和y共用一个底层数组a。 这一步把a改成了 [0 2 3 3],y也因此变成了 [3 3]

    所以再第二次x = append(x, y...)前,y就已经是 [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,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", b, len(b), cap(b), b, &b[0], &b)
    
        fmt.Printf("此时a为%#v,长度:%d,容量:%d,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", c, len(c), cap(c), c, &c[0], &c)
    
        fmt.Printf("最后b为%#v,长度:%d,容量:%d,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", b, len(b), cap(b), b, &b[0], &b)
    
        fmt.Printf("最后a为%#v,长度:%d,容量:%d,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098018
    --------
    b为[]int{0, 2},长度:2,容量:10,底层数组的内存地址的两种表示方式应该一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098060
    此时a为[]int{0},长度:1,容量:10,底层数组的内存地址的两种表示方式应该一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098018
    --------
    c为[]int{0, 3},长度:2,容量:10,底层数组的内存地址的两种表示方式应该一致0x1400009e000=0x1400009e000,sliceheader的地址0x140000980d8
    最后b为[]int{0, 3},长度:2,容量:10,底层数组的内存地址的两种表示方式应该一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098060
    最后a为[]int{0},长度:1,容量:10,底层数组的内存地址的两种表示方式应该一致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,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", b, len(b), cap(b), b, &b[0], &b)
    
        fmt.Printf("此时a为%#v,长度:%d,容量:%d,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", c, len(c), cap(c), c, &c[0], &c)
    
        fmt.Printf("最后b为%#v,长度:%d,容量:%d,底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", b, len(b), cap(b), b, &b[0], &b)
    
        fmt.Printf("最后a为%#v,长度:%d,容量:%d,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致0x140000200c8=0x140000200c8,sliceheader的地址0x1400000c048
    --------
    b为[]int{0, 2},长度:2,容量:2,底层数组的内存地址的两种表示方式应该一致0x140000200f0=0x140000200f0,sliceheader的地址0x1400000c090
    此时a为[]int{0},长度:1,容量:1,底层数组的内存地址的两种表示方式应该一致0x140000200c8=0x140000200c8,sliceheader的地址0x1400000c048
    --------
    c为[]int{0, 3},长度:2,容量:2,底层数组的内存地址的两种表示方式应该一致0x14000020120=0x14000020120,sliceheader的地址0x1400000c108
    最后b为[]int{0, 2},长度:2,容量:2,底层数组的内存地址的两种表示方式应该一致0x140000200f0=0x140000200f0,sliceheader的地址0x1400000c090
    最后a为[]int{0},长度:1,容量:1,底层数组的内存地址的两种表示方式应该一致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 底层数组的内存地址:%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli) // 0,0,底层数组的内存地址:0x0,内存地址x
    
        sli = append(sli, 0)
        fmt.Println(sli) // [0]
    
        fmt.Printf("长度:%d 容量:%d 底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 1,1,内存地址b=内存地址b(发生了扩容),内存地址x
    
        fmt.Println(unsafe.Sizeof(sli)) // 24, 其中unsafe.utp指针占8字节,len和cap也都占8个字节
    
        sli = append(sli, 1, 2, 3)
    
        fmt.Println(sli) // [0 1 2 3]
    
        fmt.Printf("长度:%d 容量:%d 底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 4,4,内存地址c=内存地址c(发生了扩容),内存地址x
    
        sli = append(sli, 6, 7)
    
        fmt.Println(sli) // [0 1 2 3 6 7]
    
        fmt.Printf("长度:%d 容量:%d 底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 6,6,内存地址d=内存地址d(发生了扩容),内存地址x
    
        sli = append(sli, 8, 9, 10)
    
        fmt.Println(sli) // [0 1 2 3 6 7 8 9 10]
    
        fmt.Printf("长度:%d 容量:%d 底层数组的内存地址的两种表示方式应该一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 9,16,内存地址e=内存地址e(发生了扩容),内存地址x
    
        fmt.Println(unsafe.Sizeof(sli)) // 24, 其中unsafe.utp指针占8字节,len和cap也都占8个字节
    
    }
    
    

    输出:

    长度:0 容量:0 底层数组的内存地址:0x0,sliceheader的地址0x1400000c048
    [0]
    长度:1 容量:1 底层数组的内存地址的两种表示方式应该一致0x140000200e0=0x140000200e0,sliceheader的地址0x1400000c048
    24
    [0 1 2 3]
    长度:4 容量:4 底层数组的内存地址的两种表示方式应该一致0x14000028100=0x14000028100,sliceheader的地址0x1400000c048
    [0 1 2 3 6 7]
    长度:6 容量:8 底层数组的内存地址的两种表示方式应该一致0x14000024500=0x14000024500,sliceheader的地址0x1400000c048
    [0 1 2 3 6 7 8 9 10]
    长度:9 容量:16 底层数组的内存地址的两种表示方式应该一致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?对于字符串和整型,表现不一样;而且为何是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,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致%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,底层数组的内存地址的两种表示方式应该一致0x140000200e0=0x140000200e0,sliceheader的地址0x1400000c048
    切片n为:[]int64{2, 3, 4},长度为3,容量为4,底层数组的内存地址的两种表示方式应该一致0x14000028100=0x14000028100,sliceheader的地址0x1400000c048
    切片n为:[]int64{2, 3, 4, 5},长度为4,容量为4,底层数组的内存地址的两种表示方式应该一致0x14000028100=0x14000028100,sliceheader的地址0x1400000c048
    切片n为:[]int64{2, 3, 4, 5, 6},长度为5,容量为8,底层数组的内存地址的两种表示方式应该一致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("------")
    
    }
    

    扩容相关的逻辑go/src/runtime/slice.go中的func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice

    但更换版本试了下,和从1.18版本开始的cap策略变更没关系

    (用1.17和1.21运行,结果是一样的)

    和element size有关,跟防止overflow以及memory alignment 。ele size 还会影响new cap

    不在这里roundup 到tcmalloc的块大小,其他内存也是浪费的。

    在此感谢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 是一个表示大小的整数值。注释中提到了三种常见的情况:

    1. et.Size 为 1 时,不需要进行除法或乘法运算。这是因为在计算机中,将一个数左移一位相当于乘以 2,右移一位相当于除以 2。因此,对于大小为 1 的情况,可以直接使用移位操作来处理,避免了除法或乘法的开销。

    2. et.Size 等于当前架构的指针大小(goarch.PtrSize)时,编译器会将除法或乘法运算优化为一个常数的位移操作。这是因为指针大小通常是2的幂次方,所以可以通过移位来进行高效的除法或乘法运算。

    3. 对于其他大小为2的幂次方的情况,使用一个可变的位移操作。这意味着将一个数左移或右移的位数是可变的,取决于 et.Size 的具体值。这种处理方式仍然利用了位移操作的高效性。

    总之,这段注释是解释了为什么针对不同的 et.Size 值采取了不同的优化策略,以提高计算效率。这些优化措施是为了充分利用位移操作和特定的数学性质,从而减少除法或乘法的开销。

    et.Size_不同,影响到最后cap的计算:如果是8字节的数据类型比如int,newcap = int(capmem / goarch.PtrSize); 如果是2的指数倍的,比如string(占16字节),newcap = int(capmem >> shift)

    et.Size_ 即元素类型占用的内存空间,常见的如 int32,存储大小:4; int64,存储大小:8; string,存储大小:16 // string类型底层是一个指针(8字节),和一个长度字段(8字节)

    详见 利用反射,探究Go语言中的数据类型

    通过在源码中添加print,大致捋清了脉络:

    基于1.21版本,switch case有四个优先级:

    1. 尺寸为1的(布尔值类型)
    2. 尺寸为8的(64位机器;32位的话为4,在此不考虑)如int64类型;
    3. 尺寸为2的指数倍的,如string类型
    4. default兜底

    最后必然还和内存分配有关系,多级 mheap,mcentral(类似于全局队列),mcache(类似于本地队列),mspan(各种尺寸的内存各有一块)

    很多个级别,涉及到向下取整,有一部分内存碎片

    相关调试代码:

    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("爽哥调试-未根据元素类型做处理前的newcap值为:", newcap, "\n")
    
        println("爽哥调试-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("爽哥调试-走到了et.Size_ == 1这里\n")
            lenmem = uintptr(oldLen)
            newlenmem = uintptr(newLen)
            capmem = roundupsize(uintptr(newcap))
            println("爽哥调试-此时capmem值为:", capmem)
            overflow = uintptr(newcap) > maxAlloc
            newcap = int(capmem)
        case et.Size_ == goarch.PtrSize:
            println("爽哥调试-走到了et.Size_ == goarch.PtrSize这里\n")
            lenmem = uintptr(oldLen) * goarch.PtrSize
            newlenmem = uintptr(newLen) * goarch.PtrSize
            capmem = roundupsize(uintptr(newcap) * goarch.PtrSize)
            println("爽哥调试-此时capmem值为:", capmem)
            overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize
            newcap = int(capmem / goarch.PtrSize)
        case isPowerOfTwo(et.Size_):
            println("爽哥调试-走到了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("爽哥调试-此时capmem值为:", capmem)
            println("爽哥调试-此时shift值为:", shift)
            overflow = uintptr(newcap) > (maxAlloc >> shift)
            newcap = int(capmem >> shift)
            capmem = uintptr(newcap) << shift
        default:
            println("爽哥调试-走到了default兜底逻辑这里\n")
            lenmem = uintptr(oldLen) * et.Size_
            newlenmem = uintptr(newLen) * et.Size_
            capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap))
            capmem = roundupsize(capmem)
            println("爽哥调试-此时capmem值为:", capmem)
            newcap = int(capmem / et.Size_)
            capmem = uintptr(newcap) * et.Size_
        }
    
        println("爽哥调试-经过一番逻辑处理后的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("爽哥调试-最终的的newcap值为:", newcap, ", capmem值为:", capmem, "\n")
        println("爽哥调试---------------------本轮扩容结束------------------\n")
        return slice{p, newLen, newcap}
    }
    
    package main
    
    import "fmt"
    
    func main() {
    
        println("~~~~~~~~~~开始进入用户代码:~~~~~~~~~~~~~~") // 前面Go底层会有很多调用到growslice的地方
        s1 := []string{"北京", "上海", "深圳"}
    
        println("~~~~~~~~~~aaaaaaaaaaaaa:~~~~~~~~~~~~~~")
        fmt.Printf("len(s1):%d,cap(s1):%d\n", len(s1), cap(s1)) // 这步会有调用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))
    }
    

    输出:

    爽哥调试-未根据元素类型做处理前的newcap值为: 1 
    
    爽哥调试-et.Size_值为: 8 
    
    爽哥调试-走到了et.Size_ == goarch.PtrSize这里
    
    爽哥调试-此时capmem值为: 8
    爽哥调试-经过一番逻辑处理后的newcap值为: 1 , capmem值为: 8 
    
    爽哥调试-最终的的newcap值为: 1 , capmem值为: 8 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    爽哥调试-未根据元素类型做处理前的newcap值为: 1 
    
    爽哥调试-et.Size_值为: 4 
    
    爽哥调试-走到了isPowerOfTwo(et.Size_)这里
    
    爽哥调试-此时capmem值为: 8
    爽哥调试-此时shift值为: 2
    爽哥调试-经过一番逻辑处理后的newcap值为: 2 , capmem值为: 8 
    
    爽哥调试-最终的的newcap值为: 2 , capmem值为: 8 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    爽哥调试-未根据元素类型做处理前的newcap值为: 4 
    
    爽哥调试-et.Size_值为: 4 
    
    爽哥调试-走到了isPowerOfTwo(et.Size_)这里
    
    爽哥调试-此时capmem值为: 16
    爽哥调试-此时shift值为: 2
    爽哥调试-经过一番逻辑处理后的newcap值为: 4 , capmem值为: 16 
    
    爽哥调试-最终的的newcap值为: 4 , capmem值为: 16 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    爽哥调试-未根据元素类型做处理前的newcap值为: 1 
    
    爽哥调试-et.Size_值为: 8 
    
    爽哥调试-走到了et.Size_ == goarch.PtrSize这里
    
    爽哥调试-此时capmem值为: 8
    爽哥调试-经过一番逻辑处理后的newcap值为: 1 , capmem值为: 8 
    
    爽哥调试-最终的的newcap值为: 1 , capmem值为: 8 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    爽哥调试-未根据元素类型做处理前的newcap值为: 2 
    
    爽哥调试-et.Size_值为: 8 
    
    爽哥调试-走到了et.Size_ == goarch.PtrSize这里
    
    爽哥调试-此时capmem值为: 16
    爽哥调试-经过一番逻辑处理后的newcap值为: 2 , capmem值为: 16 
    
    爽哥调试-最终的的newcap值为: 2 , capmem值为: 16 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    爽哥调试-未根据元素类型做处理前的newcap值为: 4 
    
    爽哥调试-et.Size_值为: 8 
    
    爽哥调试-走到了et.Size_ == goarch.PtrSize这里
    
    爽哥调试-此时capmem值为: 32
    爽哥调试-经过一番逻辑处理后的newcap值为: 4 , capmem值为: 32 
    
    爽哥调试-最终的的newcap值为: 4 , capmem值为: 32 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    爽哥调试-未根据元素类型做处理前的newcap值为: 82 
    
    爽哥调试-et.Size_值为: 16 
    
    爽哥调试-走到了isPowerOfTwo(et.Size_)这里
    
    爽哥调试-此时capmem值为: 1408
    爽哥调试-此时shift值为: 4
    爽哥调试-经过一番逻辑处理后的newcap值为: 88 , capmem值为: 1408 
    
    爽哥调试-最终的的newcap值为: 88 , capmem值为: 1408 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    爽哥调试-未根据元素类型做处理前的newcap值为: 8 
    
    爽哥调试-et.Size_值为: 8 
    
    爽哥调试-走到了et.Size_ == goarch.PtrSize这里
    
    爽哥调试-此时capmem值为: 64
    爽哥调试-经过一番逻辑处理后的newcap值为: 8 , capmem值为: 64 
    
    爽哥调试-最终的的newcap值为: 8 , capmem值为: 64 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    爽哥调试-未根据元素类型做处理前的newcap值为: 1 
    
    爽哥调试-et.Size_值为: 16 
    
    爽哥调试-走到了isPowerOfTwo(et.Size_)这里
    
    爽哥调试-此时capmem值为: 16
    爽哥调试-此时shift值为: 4
    爽哥调试-经过一番逻辑处理后的newcap值为: 1 , capmem值为: 16 
    
    爽哥调试-最终的的newcap值为: 1 , capmem值为: 16 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    ~~~~~~~~~~开始进入用户代码:~~~~~~~~~~~~~~
    ~~~~~~~~~~aaaaaaaaaaaaa:~~~~~~~~~~~~~~
    爽哥调试-未根据元素类型做处理前的newcap值为: 1 
    
    爽哥调试-et.Size_值为: 8 
    
    爽哥调试-走到了et.Size_ == goarch.PtrSize这里
    
    爽哥调试-此时capmem值为: 8
    爽哥调试-经过一番逻辑处理后的newcap值为: 1 , capmem值为: 8 
    
    爽哥调试-最终的的newcap值为: 1 , capmem值为: 8 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    爽哥调试-未根据元素类型做处理前的newcap值为: 8 
    
    爽哥调试-et.Size_值为: 1 
    
    爽哥调试-走到了et.Size_ == 1这里
    
    爽哥调试-此时capmem值为: 8
    爽哥调试-经过一番逻辑处理后的newcap值为: 8 , capmem值为: 8 
    
    爽哥调试-最终的的newcap值为: 8 , capmem值为: 8 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    爽哥调试-未根据元素类型做处理前的newcap值为: 16 
    
    爽哥调试-et.Size_值为: 1 
    
    爽哥调试-走到了et.Size_ == 1这里
    
    爽哥调试-此时capmem值为: 16
    爽哥调试-经过一番逻辑处理后的newcap值为: 16 , capmem值为: 16 
    
    爽哥调试-最终的的newcap值为: 16 , capmem值为: 16 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    爽哥调试-未根据元素类型做处理前的newcap值为: 32 
    
    爽哥调试-et.Size_值为: 1 
    
    爽哥调试-走到了et.Size_ == 1这里
    
    爽哥调试-此时capmem值为: 32
    爽哥调试-经过一番逻辑处理后的newcap值为: 32 , capmem值为: 32 
    
    爽哥调试-最终的的newcap值为: 32 , capmem值为: 32 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    len(s1):3,cap(s1):3
    ~~~~~~~~~~bbbbbbbbbbbbbb:~~~~~~~~~~~~~~
    
    ================正式开始:===============
    爽哥调试-未根据元素类型做处理前的newcap值为: 11 
    
    爽哥调试-et.Size_值为: 16 
    
    爽哥调试-走到了isPowerOfTwo(et.Size_)这里
    
    爽哥调试-此时capmem值为: 176
    爽哥调试-此时shift值为: 4
    爽哥调试-经过一番逻辑处理后的newcap值为: 11 , capmem值为: 176 
    
    爽哥调试-最终的的newcap值为: 11 , capmem值为: 176 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    ~~~~~~~~~~cccccccccccccc:~~~~~~~~~~~~~~
    长度为: 11 容量为: 11
    ~~~~~~~~~~dddddddddddddd:~~~~~~~~~~~~~~
    ------
    长度为: 3 容量为: 3
    爽哥调试-未根据元素类型做处理前的newcap值为: 11 
    
    爽哥调试-et.Size_值为: 8 
    
    爽哥调试-走到了et.Size_ == goarch.PtrSize这里
    
    爽哥调试-此时capmem值为: 96
    爽哥调试-经过一番逻辑处理后的newcap值为: 12 , capmem值为: 96 
    
    爽哥调试-最终的的newcap值为: 12 , capmem值为: 96 
    
    爽哥调试---------------------本轮扩容结束------------------
    
    长度为: 11 容量为: 12
    

    你真的了解go语言中的切片吗? 最后 3.12 问题12差不多


    case5: 初始容量的确定

    • 通过s := make([]int,10)这种方式,如果没有指定cap的值,则默认与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,底层数组的内存地址为%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,底层数组的内存地址为%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(发生了扩容), 地址z
        fmt.Printf("切片demo3为:%#v,长度为%d,容量为%d,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为0x140000260f0,sliceheader的地址为0x1400000c048
    切片demo2为:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0},长度为9,容量为9,底层数组的内存地址为0x140000260f0,sliceheader的地址为0x1400000c060
    -------
    切片demo3为:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 1},长度为10,容量为18,底层数组的内存地址为0x14000102000,sliceheader的地址为0x1400000c0d8
    切片demo4为:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3},长度为13,容量为18,底层数组的内存地址为0x14000102000,sliceheader的地址为0x1400000c120
    

    为什么初始容量为9?

    为什么后面扩容是18而不是16?

    通过 append 操作,可以在 slice 末尾,额外新增一个元素. 需要注意,这里的末尾指的是针对 slice 的长度 len 而言. 这个过程中倘若发现 slice 的剩余容量已经不足了,则会对 slice 进行扩容

    当 slice 当前的长度 len 与容量 cap 相等时,下一次 append 操作就会引发一次切片扩容

    <font size=3 color="orange">

    切片的扩容流程源码位于 runtime/slice.go 文件的 growslice 方法当中,其中核心步骤如下:

    • 倘若扩容后预期的新容量小于原切片的容量,则 panic

    • 倘若切片元素大小为 0(元素类型为 struct{}),则直接复用一个全局的 zerobase 实例,直接返回

    • 倘若预期的新容量超过老容量的两倍,则直接采用预期的新容量

    • 倘若老容量小于 256,则直接采用老容量的2倍作为新容量

    • 倘若老容量已经大于等于 256,则在老容量的基础上扩容 1/4 的比例并且累加上 192 的数值,持续这样处理,直到得到的新容量已经大于等于预期的新容量为止

    • 结合 mallocgc 流程中,对内存分配单元 mspan 的等级制度,推算得到实际需要申请的内存空间大小

    • 调用 mallocgc,对新切片进行内存初始化

    • 调用 memmove 方法,将老切片中的内容拷贝到新切片中

    • 返回扩容后的新切片

    </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 经过如上逻辑后,还要再根据元素类型,做一次处理。详见case4中的源码调试

    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,底层数组的内存地址为%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,底层数组的内存地址为%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(发生了扩容)
        fmt.Printf("切片demo3为:%#v,长度为%d,容量为%d,底层数组的内存地址为%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(未发生扩容)
        fmt.Printf("切片demo4为:%#v,长度为%d,容量为%d,底层数组的内存地址为%p\n", demo4, len(demo4), cap(demo4), demo4)
    
        fmt.Println()
    
        fmt.Println("---------------------")
    
        // 对于这种sli2 = append(sli1,6,6,6),如果没发生扩容,sli1和sli2底层数组一样
    
        // 对于sli1 = append(sli,6,6), sli2 = append(sli,8,8),即便容量一样,sli1和sli2底层数组也不一样..
    
        //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,底层数组的内存地址为%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(发生了扩容)
        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,底层数组的内存地址为%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(发生了扩容)
        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,底层数组的内存地址为%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(发生了扩容)
        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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%p\n", sli, len(sli), cap(sli), sli)
    
    }
    

    输出:

    切片demo为:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},长度为10,容量为10,底层数组的内存地址为0x140000ba000
    切片demo2为:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},长度为10,容量为10,底层数组的内存地址为0x140000ba000
    切片demo3为:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},长度为11,容量为20,底层数组的内存地址为0x140000c2000
    切片demo4为:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3},长度为14,容量为20,底层数组的内存地址为0x140000c2000
    
    ---------------------
    切片sli为:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},长度为11,容量为11,底层数组的内存地址为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,底层数组的内存地址为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,底层数组的内存地址为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,底层数组的内存地址为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,底层数组的内存地址为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,底层数组的内存地址为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,底层数组的内存地址为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,底层数组的内存地址为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,底层数组的内存地址为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,底层数组的内存地址为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,底层数组的内存地址为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,底层数组的内存地址为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,底层数组的内存地址为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,底层数组的内存地址为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
    

    两种不同的声明方式,对初始容量的影响

    (图片来自网络)

    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,底层数组的内存地址为%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(发生了扩容)
        fmt.Printf("切片j为:%#v,长度为%d,容量为%d,底层数组的内存地址为%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,底层数组的内存地址为%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(发生了扩容)
        fmt.Printf("切片g为:%#v,长度为%d,容量为%d,底层数组的内存地址为%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,底层数组的内存地址为%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(发生了扩容)
        fmt.Printf("切片h为:%#v,长度为%d,容量为%d,底层数组的内存地址为%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,底层数组的内存地址为%p\n", i, len(i), cap(i), i)
    
        fmt.Println()
    
        fmt.Println("最后的结果:")
        fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h) // 因为发生了扩容,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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%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,底层数组的内存地址为%p\n", i, len(i), cap(i), i)
    
        fmt.Println()
    
        fmt.Println("最后的结果:")
        fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h) // i, j, g, h共用一个底层数组,改值 会相互影响
    }
    

    番外: 与append无关的一些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

    并发写入

    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或较小的数;加上这行,也小于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) // 不加这一行,结果一般小于100;100次加锁解锁操作,1ms内完不成
        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) // 不加这一行,结果一般小于100;100次加锁解锁操作,1ms内完不成
        fmt.Println(len(sli))
    
    }
    

    把锁初始化的操作放在循环内是不行的,最后的结果一定小于100.

    要放到全局,或者循环体外,只初始化一把锁,而不是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类型,否则会当成int,最终结果为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多平台发布

    相关文章

      网友评论

          本文标题:Go slice扩容N连问

          本文链接:https://www.haomeiwen.com/subject/ieizydtx.html