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
是一个示意大小的整数值。正文中提到了三种常见的状况:
- 当
et.Size
为 1 时,不须要进行除法或乘法运算。这是因为在计算机中,将一个数左移一位相当于乘以 2,右移一位相当于除以 2。因而,对于大小为 1 的状况,能够间接应用移位操作来解决,防止了除法或乘法的开销。 - 当
et.Size
等于以后架构的指针大小(goarch.PtrSize
)时,编译器会将除法或乘法运算优化为一个常数的位移操作。这是因为指针大小通常是 2 的幂次方,所以能够通过移位来进行高效的除法或乘法运算。 - 对于其余大小为 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 的(布尔值类型)
- 尺寸为 8 的 (64 位机器;32 位的话为 4,在此不思考) 如 int64 类型;
- 尺寸为 2 的指数倍的,如 string 类型
- 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 多平台公布