fmt.Printf("%p\n", &xxx)的打印問題
后面的參數(shù)必須為 指針類型,否則IDE會有提示,運行后打出來的是%!p(int=0)



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

https://github.com/golang/go/blob/2a8969cb365a5539b8652d5ac1588aaef78d3e16/src/fmt/print.go#L553
通過查看源碼及試驗可知,fmt.Printf("%p",&sli)得到的是sliceHeader的地址,
想獲取切片底層數(shù)組的地址,要fmt.Printf("%p",&sli[0]),或者fmt.Printf("%p",sli)? (因為sliceheader的第一個字段是底層數(shù)組的pointer)
對任何變量x都可以&x,即這個變量在內(nèi)存里的地址。但如果x本身就是指針類型,fmt.Printf("%p",x)打印的就是這個指針類型對應(yīng)的內(nèi)容,如果fmt.Printf("%p",&x),那就是獲取這個指針類型在內(nèi)存里的地址,結(jié)果也是一個指針類型
// runtime/slice.go下不可導(dǎo)出的
type slice struct {
array unsafe.Pointer
len int
cap int
}
// reflect包可以導(dǎo)出的
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
下面正篇開始
case1: 當(dāng)作為參數(shù)傳遞
共享底層數(shù)組,修改后會影響原值
package main
import (
"fmt"
)
func main() {
sli := make([]string, 1)
sli[0] = "宋江"
fmt.Println("slice is:", sli) // ["宋江"]
fmt.Printf("原始sli的長度%d,容量%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 1 1 內(nèi)存地址a=內(nèi)存地址a, 內(nèi)存地址x
f1(sli)
fmt.Println("slice is:", sli) // ["晁蓋"]
fmt.Printf("調(diào)用f1()之后sli的長度%d,容量%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 1 1 內(nèi)存地址a=內(nèi)存地址a,內(nèi)存地址x (因為都是sli這個變量)
sli666 := sli
fmt.Printf("sli666的長度%d,容量%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli666), cap(sli666), sli666, &sli666[0], &sli666) // 1 1 內(nèi)存地址a=內(nèi)存地址a,內(nèi)存地址y
}
func f1(sli1 []string) []string {
sli1[0] = "晁蓋"
return sli1
}
輸出:
slice is: [宋江]
原始sli的長度1,容量1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000010230=0x14000010230,sliceheader的地址0x1400000c048
slice is: [晁蓋]
調(diào)用f1()之后sli的長度1,容量1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000010230=0x14000010230,sliceheader的地址0x1400000c048
sli666的長度1,容量1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000010230=0x14000010230,sliceheader的地址0x1400000c0c0
再如:
package main
import "fmt"
// append無論如何都是從slice的尾部開始追加數(shù)據(jù); 如果有append操作,很可能會引發(fā)擴容,要特別注意
func main() {
sli := make([]string, 1)
sli[0] = "宋江"
fmt.Printf("[main]原始sli為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:%p=%p,sliceheader的地址%p\n", sli, len(sli), cap(sli), sli, &sli[0], &sli)
// [main]原始sli為[]string{"宋江"},長度:1,容量:1,內(nèi)存地址a=內(nèi)存地址a,內(nèi)存地址x
f2(sli)
fmt.Printf("[main]調(diào)用f2()之后sli為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:%p=%p,sliceheader的地址%p\n", sli, len(sli), cap(sli), sli, &sli[0], &sli)
// [main]調(diào)用f2()之后sli為[]string{"晁蓋"},長度:1,容量:1,內(nèi)存地址a=內(nèi)存地址a,內(nèi)存地址x
// 可見,只可能會影響底層數(shù)組的值,**不會影響長度和容量**
}
func f2(sli1 []string) []string {
fmt.Printf("[f2]f2中append之前sli1的長度%d,容量%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:%p=%p,sliceheader的地址%p\n", len(sli1), cap(sli1), sli1, &sli1[0], &sli1)
// [f2]f2中append之前sli1的長度1,容量1,內(nèi)存地址a=內(nèi)存地址a,內(nèi)存地址y(可以看出是值傳遞)
sli1[0] = "晁蓋" // 此時沒有擴容,sli1和main中的sli地址一樣,修改sli1[0]自然會影響main
sli1 = append(sli1, "盧俊義", "吳用", "公孫勝", "關(guān)勝")
// 如果將上面的sli1[0] = "晁蓋"去掉,而在下方賦值,此時sli1和main中的sli內(nèi)存地址不同,此時再修改sli1[0]不會影響到main
//sli1[0] = "晁蓋"
fmt.Printf("[f2]f2中append之后sli1的長度%d,容量%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:%p=%p,sliceheader的地址%p\n", len(sli1), cap(sli1), sli1, &sli1[0], &sli1)
// [f2]f2中append之后sli1的長度5,容量5,內(nèi)存地址b=內(nèi)存地址b,內(nèi)存地址y(可以看出是值傳遞)
return sli1
}
// append一定會改變原始slice的內(nèi)存地址嗎? 不一定,不發(fā)生擴容就不會改變~
輸出:
[main]原始sli為[]string{"宋江"},長度:1,容量:1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:0x14000010230=0x14000010230,sliceheader的地址0x1400000c048
[f2]f2中append之前sli1的長度1,容量1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:0x14000010230=0x14000010230,sliceheader的地址0x1400000c090
[f2]f2中append之后sli1的長度5,容量5,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:0x14000064050=0x14000064050,sliceheader的地址0x1400000c090
[main]調(diào)用f2()之后sli為[]string{"晁蓋"},長度:1,容量:1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致:0x14000010230=0x14000010230,sliceheader的地址0x1400000c048
通過索引修改切片元素會影響原切片,但通過append追加元素,則不會(改變原切片的長度和容量)
Go中參數(shù)傳遞都是值傳遞,但當(dāng)參數(shù)為引用類型如slice等時需要注意
package main
import "fmt"
func main() {
i := make([]int, 10, 12)
i1 := i[8:]
// [0 0] 2 4 地址xxxxxxx
fmt.Printf("%v len:%d cap:%d ptr:%p\n", i1, len(i1), cap(i1), i1)
changeSlice(i1)
// 此時i1變?yōu)榱?[-1 0] 2 4
// 因為和i底層數(shù)組是一個,所以i也會改變
fmt.Println(i) // [0 0 0 0 0 0 0 0 -1 0]
fmt.Println("--------")
j := make([]int, 10, 12)
j1 := j[8:] // [0 0] 2 4
changeSlice2(j1) // [0 0 10] 3 4 ---為什么不對??
// [0 0 0 0 0 0 0 0 0 0] 10 12
fmt.Printf("j: %v, len of j: %d, cap of j: %d\n", j, len(j), cap(j))
// [0 0 10] 3 4 ---為什么不對??
fmt.Printf("j1: %v, len of j1: %d, cap of j1: %d\n", j1, len(j1), cap(j1))
}
func changeSlice(s1 []int) {
s1[0] = -1
}
func changeSlice2(s1 []int) {
s1 = append(s1, 10)
}
添加一些調(diào)試代碼:
package main
import "fmt"
func main() {
i := make([]int, 10, 12)
fmt.Printf("[main] i: %v len:%d cap:%d ptr:%p sliceheader的地址%p\n", i, len(i), cap(i), i, &i)
i1 := i[8:]
fmt.Printf("[main] i1: %v len:%d cap:%d ptr:%p sliceheader的地址%p\n", i1, len(i1), cap(i1), i1, &i1)
changeSlice(i1)
fmt.Printf("[main] i1: %v len:%d cap:%d ptr:%p sliceheader的地址%p\n", i1, len(i1), cap(i1), i1, &i1)
fmt.Println(i)
fmt.Printf("[main] i: %v len:%d cap:%d ptr:%p sliceheader的地址%p\n", i, len(i), cap(i), i, &i)
fmt.Println("--------")
j := make([]int, 10, 12)
fmt.Printf("[main] j: %v, len of j: %d, cap of j: %d,ptr:%p sliceheader的地址%p\n", j, len(j), cap(j), j, &j)
j1 := j[8:]
fmt.Printf("[main] j1: %v, len of j1: %d, cap of j1: %d, ptr:%p sliceheader的地址%p\n", j1, len(j1), cap(j1), j1, &j1)
changeSlice2(j1) // [0 0 10] 3 4 ---為什么不對??
fmt.Printf("[main] j1: %v, len of j1: %d, cap of j1: %d, ptr:%p sliceheader的地址%p\n", j1, len(j1), cap(j1), j1, &j1)
fmt.Println(j)
fmt.Printf("[main] j: %v len:%d cap:%d ptr:%p sliceheader的地址%p\n", j, len(j), cap(j), j, &j)
}
func changeSlice(s1 []int) {
fmt.Printf("[changeSlice] s1: %v len:%d cap:%d ptr:%p sliceheader的地址%p\n", s1, len(s1), cap(s1), s1, &s1)
s1[0] = -1
fmt.Printf("[changeSlice] s1: %v len:%d cap:%d ptr:%p sliceheader的地址%p\n", s1, len(s1), cap(s1), s1, &s1)
}
func changeSlice2(s1 []int) {
fmt.Printf("[changeSlice2] s1: %v len:%d cap:%d ptr:%p sliceheader的地址%p\n", s1, len(s1), cap(s1), s1, &s1)
s1 = append(s1, 10)
fmt.Printf("[changeSlice2] s1: %v len:%d cap:%d ptr:%p sliceheader的地址%p\n", s1, len(s1), cap(s1), s1, &s1)
}
輸出為:
[main] i: [0 0 0 0 0 0 0 0 0 0] len:10 cap:12 ptr:0x1400010e060 sliceheader的地址0x1400011a030
[main] i1: [0 0] len:2 cap:4 ptr:0x1400010e0a0 sliceheader的地址0x1400011a078
[changeSlice] s1: [0 0] len:2 cap:4 ptr:0x1400010e0a0 sliceheader的地址0x1400011a0c0
[changeSlice] s1: [-1 0] len:2 cap:4 ptr:0x1400010e0a0 sliceheader的地址0x1400011a0c0
[main] i1: [-1 0] len:2 cap:4 ptr:0x1400010e0a0 sliceheader的地址0x1400011a078
[0 0 0 0 0 0 0 0 -1 0]
[main] i: [0 0 0 0 0 0 0 0 -1 0] len:10 cap:12 ptr:0x1400010e060 sliceheader的地址0x1400011a030
--------
[main] j: [0 0 0 0 0 0 0 0 0 0], len of j: 10, cap of j: 12,ptr:0x1400010e0c0 sliceheader的地址0x1400011a1b0
[main] j1: [0 0], len of j1: 2, cap of j1: 4, ptr:0x1400010e100 sliceheader的地址0x1400011a1f8
[changeSlice2] s1: [0 0] len:2 cap:4 ptr:0x1400010e100 sliceheader的地址0x1400011a240
[changeSlice2] s1: [0 10] len:2 cap:4 ptr:0x1400010e100 sliceheader的地址0x1400011a240
[main] j1: [0 10], len of j1: 2, cap of j1: 4, ptr:0x1400010e100 sliceheader的地址0x1400011a1f8
[0 0 0 0 0 0 0 0 0 10]
[main] j: [0 0 0 0 0 0 0 0 0 10] len:10 cap:12 ptr:0x1400010e0c0 sliceheader的地址0x1400011a1b0
只能說,通過索引修改切片元素,和通過append追加元素,表現(xiàn)完全不同:
因為append一定至少改變了長度(甚至也改了容量),這種操作只會影響子方法中的,不會影響原值
但如果是修改,子方法修改了某個索引下元素的值,父方法也會受到影響
case2: 擴容
通過 append 操作,可以在 slice 末尾,額外新增一個元素. 需要注意,這里的末尾指的是針對 slice 的長度 len 而言. 這個過程中倘若發(fā)現(xiàn) slice 的剩余容量已經(jīng)不足了,則會對 slice 進行擴容
當(dāng) slice 當(dāng)前的長度 len 與容量 cap 相等時,下一次 append 操作就會引發(fā)一次切片擴容
<font size=3 color="orange">
切片的擴容流程源碼位于 runtime/slice.go 文件的 growslice 方法當(dāng)中,其中核心步驟如下:
? 倘若擴容后預(yù)期的新容量小于原切片的容量,則 panic
? 倘若切片元素大小為 0(元素類型為 struct{}),則直接復(fù)用一個全局的 zerobase 實例,直接返回
? 倘若預(yù)期的新容量超過老容量的兩倍,則直接采用預(yù)期的新容量
? 倘若老容量小于 256,則直接采用老容量的2倍作為新容量
? 倘若老容量已經(jīng)大于等于 256,則在老容量的基礎(chǔ)上擴容 1/4 的比例并且累加上 192 的數(shù)值,持續(xù)這樣處理,直到得到的新容量已經(jīng)大于等于預(yù)期的新容量為止
? 結(jié)合 mallocgc 流程中,對內(nèi)存分配單元 mspan 的等級制度,推算得到實際需要申請的內(nèi)存空間大小
? 調(diào)用 mallocgc,對新切片進行內(nèi)存初始化
? 調(diào)用 memmove 方法,將老切片中的內(nèi)容拷貝到新切片中
? 返回擴容后的新切片
</font>
以上內(nèi)容來自 你真的了解go語言中的切片嗎?
append可能引發(fā)擴容,如果發(fā)生擴容(即cap發(fā)生變化),slice底層數(shù)組的內(nèi)存地址就變了~
package main
import "fmt"
// append一定會改變原始slice底層數(shù)組的內(nèi)存地址嗎。。不一定,沒有發(fā)生擴容就不需要
// https://www.zhihu.com/question/265386326/answer/2321716435
func main() {
names := make([]int, 3)
fmt.Printf("切片為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", names, len(names), cap(names), names, &names[0], &names)
fmt.Println("-------")
for i := 1; i < 6; i++ {
fmt.Printf("切片為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", names, len(names), cap(names), names, &names[0], &names)
names = append(names, i)
}
fmt.Println("-------")
fmt.Printf("切片為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", names, len(names), cap(names), names, &names[0], &names)
}
輸出:
切片為:[]int{0, 0, 0},長度為3,容量為3,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000130000=0x14000130000,sliceheader的地址0x14000114030
-------
切片為:[]int{0, 0, 0},長度為3,容量為3,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000130000=0x14000130000,sliceheader的地址0x14000114030
切片為:[]int{0, 0, 0, 1},長度為4,容量為6,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400012e030=0x1400012e030,sliceheader的地址0x14000114030
切片為:[]int{0, 0, 0, 1, 2},長度為5,容量為6,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400012e030=0x1400012e030,sliceheader的地址0x14000114030
切片為:[]int{0, 0, 0, 1, 2, 3},長度為6,容量為6,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400012e030=0x1400012e030,sliceheader的地址0x14000114030
切片為:[]int{0, 0, 0, 1, 2, 3, 4},長度為7,容量為12,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000102060=0x14000102060,sliceheader的地址0x14000114030
-------
切片為:[]int{0, 0, 0, 1, 2, 3, 4, 5},長度為8,容量為12,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000102060=0x14000102060,sliceheader的地址0x14000114030
case3:由一個數(shù)組得到一個切片,以及兩個切片之間更復(fù)雜的引用
ppackage main
import (
"fmt"
)
func main() {
a := [...]int{0, 1, 2, 3}
fmt.Printf("數(shù)組為:%#v,長度為%d,容量為%d,該數(shù)組的內(nèi)存地址為:%p", a, len(a), cap(a), &a) // [4]int{0 1 2 3}, 4, 4, 地址a
fmt.Println()
x := a[:1]
fmt.Printf("切片x為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", x, len(x), cap(x), x, &x[0], &x) // []int{0}, 1, 4(容量為底層數(shù)組的長度),地址a (也是底層數(shù)組的地址,而不是x這個切片本身的地址)=地址a, 地址x
y := a[2:]
fmt.Printf("切片y為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", y, len(y), cap(y), y, &y[0], &y) // []int{2 3}, 2, 2(容量為2?。?!對數(shù)組切一刀留前面的和留后面的對容量來說不一樣), 地址a (同上例)=地址a, 地址y
x = append(x, y...)
fmt.Printf("切片x為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", x, len(x), cap(x), x, &x[0], &x) // []int{0 2 3}, 3, 4, 地址a(依然沒有擴容)=地址a, 地址x
x = append(x, y...)
fmt.Printf("切片x為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", x, len(x), cap(x), x, &x[0], &x) // ?。?! []int{0 2 3 3 3}, 5, 8 地址b=地址b(因為擴容了,底層數(shù)組就變了),地址x ?。?!
/* 錯誤!
由上面代碼可知,append(sli1,sli2...),并不等價與sli1 = append(sli1,sli2[0],sli2[1]..,sli2[最后一個元素]),而是類似(無論從最后切片的容量,還是append進去的元素的值)
for _,ele := range sli2 {
sli1 = append(sli1,ele)
}
錯誤!
*/
fmt.Println("--------")
fmt.Println(a, x)
}
輸出:
數(shù)組為:[4]int{0, 1, 2, 3},長度為4,容量為4,該數(shù)組的內(nèi)存地址為:0x140000280e0
切片x為:[]int{0},長度為1,容量為4,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000280e0=0x140000280e0,sliceheader的地址0x1400000c048
切片y為:[]int{2, 3},長度為2,容量為2,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000280f0=0x140000280f0,sliceheader的地址0x1400000c090
切片x為:[]int{0, 2, 3},長度為3,容量為4,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000280e0=0x140000280e0,sliceheader的地址0x1400000c048
切片x為:[]int{0, 2, 3, 3, 3},長度為5,容量為8,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000024500=0x14000024500,sliceheader的地址0x1400000c048
--------
[0 2 3 3] [0 2 3 3 3]
由最后一步的輸出,能否認(rèn)為append(sli1,sli2...),并不等價與sli1 = append(sli1,sli2[0],sli2[1]..,sli2[最后一個元素]),而是類似(無論從最后切片的容量,還是append進去的元素的值)? 即類似
for _,ele := range sli2 {
sli1 = append(sli1,ele)
}
寫demo試一下:
package main
import "fmt"
func main() {
sli1 := []int{0, 1}
sli2 := []int{6, 7, 8}
sli1 = append(sli1, sli2...)
fmt.Printf("%#v,cap:%d\n", sli1, cap(sli1))
}
輸出: []int{0, 1, 6, 7, 8},cap:6
看起來又是和sli1 = append(sli1,6,7,8)結(jié)果一致的
其實,問題出在第一次x = append(x, y...)這一步
此時x沒有擴容,和y共用一個底層數(shù)組a。 這一步把a改成了 [0 2 3 3],y也因此變成了 [3 3]
所以再第二次x = append(x, y...)前,y就已經(jīng)是 [3 3]了
所以 append(sli1,sli2...),還是等價于append(sli1,sli2[0],sli2[1]..,sli2[最后一個元素])的

package main
import "fmt"
func main() {
a := make([]int, 1, 10)
b := append(a, 2)
//c := append(a, 3)
fmt.Println(a) // [0]
fmt.Println(b) // [0, 2]
fmt.Println(a) // 輸出什么?
//fmt.Println(c)
}
輸出: [0]
package main
import "fmt"
func main() {
a := make([]int, 1, 10)
b := append(a, 2)
c := append(a, 3)
fmt.Println(a) // [0]
fmt.Println(b) // 輸出什么?
fmt.Println(a) // 輸出什么?
fmt.Println(c) // 輸出什么?
}
輸出: [0 3] ? [0] ? [0 3]
package main
import "fmt"
func main() {
a := make([]int, 1, 10)
fmt.Printf("a為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", a, len(a), cap(a), a, &a[0], &a)
fmt.Println("--------")
b := append(a, 2)
fmt.Printf("b為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", b, len(b), cap(b), b, &b[0], &b)
fmt.Printf("此時a為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", a, len(a), cap(a), a, &a[0], &a)
fmt.Println("--------")
c := append(a, 3)
fmt.Printf("c為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", c, len(c), cap(c), c, &c[0], &c)
fmt.Printf("最后b為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", b, len(b), cap(b), b, &b[0], &b)
fmt.Printf("最后a為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", a, len(a), cap(a), a, &a[0], &a)
fmt.Println(a) // [0]
fmt.Println(b) // [0, 2]
fmt.Println(a) // 輸出什么?
fmt.Println(c) // 輸出什么?
}
輸出:
a為[]int{0},長度:1,容量:10,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098018
--------
b為[]int{0, 2},長度:2,容量:10,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098060
此時a為[]int{0},長度:1,容量:10,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098018
--------
c為[]int{0, 3},長度:2,容量:10,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400009e000=0x1400009e000,sliceheader的地址0x140000980d8
最后b為[]int{0, 3},長度:2,容量:10,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098060
最后a為[]int{0},長度:1,容量:10,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400009e000=0x1400009e000,sliceheader的地址0x14000098018
[0]
[0 3]
[0]
[0 3]
package main
import "fmt"
func main() {
a := make([]int, 1, 1)
fmt.Printf("a為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", a, len(a), cap(a), a, &a[0], &a)
fmt.Println("--------")
b := append(a, 2)
fmt.Printf("b為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", b, len(b), cap(b), b, &b[0], &b)
fmt.Printf("此時a為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", a, len(a), cap(a), a, &a[0], &a)
fmt.Println("--------")
c := append(a, 3)
fmt.Printf("c為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", c, len(c), cap(c), c, &c[0], &c)
fmt.Printf("最后b為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", b, len(b), cap(b), b, &b[0], &b)
fmt.Printf("最后a為%#v,長度:%d,容量:%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", a, len(a), cap(a), a, &a[0], &a)
fmt.Println(a) // [0]
fmt.Println(b) // [0, 2]
fmt.Println(a) // 輸出什么?
fmt.Println(c) // 輸出什么?
}
輸出:
a為[]int{0},長度:1,容量:1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200c8=0x140000200c8,sliceheader的地址0x1400000c048
--------
b為[]int{0, 2},長度:2,容量:2,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200f0=0x140000200f0,sliceheader的地址0x1400000c090
此時a為[]int{0},長度:1,容量:1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200c8=0x140000200c8,sliceheader的地址0x1400000c048
--------
c為[]int{0, 3},長度:2,容量:2,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000020120=0x14000020120,sliceheader的地址0x1400000c108
最后b為[]int{0, 2},長度:2,容量:2,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200f0=0x140000200f0,sliceheader的地址0x1400000c090
最后a為[]int{0},長度:1,容量:1,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200c8=0x140000200c8,sliceheader的地址0x1400000c048
[0]
[0 2]
[0]
[0 3]
case4: 一次壓入多個 與 多次壓入一個;元素類型對擴容的影響
為什么不同類型的切片,append之后的len和cap不一樣?
package main
import "fmt"
func main() {
s1 := []string{"北京", "上海", "深圳"}
fmt.Printf("len(s1):%d,cap(s1):%d\n", len(s1), cap(s1))
s1 = append(s1, "廣州", "成都", "重慶", "石家莊", "保定", "邢臺", "張家口", "濟南")
fmt.Printf("len(s1):%d,cap(s1):%d\n", len(s1), cap(s1))
fmt.Println("------")
s2 := []int{1, 2, 3}
fmt.Printf("len(s2):%d,cap(s2):%d\n", len(s2), cap(s2))
s2 = append(s2, 4, 5, 6, 7, 8, 9, 10, 11)
fmt.Printf("len(s2):%d,cap(s2):%d\n", len(s2), cap(s2))
}
輸出:
len(s1):3,cap(s1):3
len(s1):11,cap(s1):11
------
len(s2):3,cap(s2):3
len(s2):11,cap(s2):12
為什么不同類型不一樣?
package main
import (
"fmt"
"unsafe"
)
func main() {
var sli []int64
// 對于未初始化的slice,使用 &sli[0]會panic
fmt.Printf("長度:%d 容量:%d 底層數(shù)組的內(nèi)存地址:%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli) // 0,0,底層數(shù)組的內(nèi)存地址:0x0,內(nèi)存地址x
sli = append(sli, 0)
fmt.Println(sli) // [0]
fmt.Printf("長度:%d 容量:%d 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 1,1,內(nèi)存地址b=內(nèi)存地址b(發(fā)生了擴容),內(nèi)存地址x
fmt.Println(unsafe.Sizeof(sli)) // 24, 其中unsafe.utp指針占8字節(jié),len和cap也都占8個字節(jié)
sli = append(sli, 1, 2, 3)
fmt.Println(sli) // [0 1 2 3]
fmt.Printf("長度:%d 容量:%d 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 4,4,內(nèi)存地址c=內(nèi)存地址c(發(fā)生了擴容),內(nèi)存地址x
sli = append(sli, 6, 7)
fmt.Println(sli) // [0 1 2 3 6 7]
fmt.Printf("長度:%d 容量:%d 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 6,6,內(nèi)存地址d=內(nèi)存地址d(發(fā)生了擴容),內(nèi)存地址x
sli = append(sli, 8, 9, 10)
fmt.Println(sli) // [0 1 2 3 6 7 8 9 10]
fmt.Printf("長度:%d 容量:%d 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", len(sli), cap(sli), sli, &sli[0], &sli) // 9,16,內(nèi)存地址e=內(nèi)存地址e(發(fā)生了擴容),內(nèi)存地址x
fmt.Println(unsafe.Sizeof(sli)) // 24, 其中unsafe.utp指針占8字節(jié),len和cap也都占8個字節(jié)
}
輸出:
長度:0 容量:0 底層數(shù)組的內(nèi)存地址:0x0,sliceheader的地址0x1400000c048
[0]
長度:1 容量:1 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200e0=0x140000200e0,sliceheader的地址0x1400000c048
24
[0 1 2 3]
長度:4 容量:4 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000028100=0x14000028100,sliceheader的地址0x1400000c048
[0 1 2 3 6 7]
長度:6 容量:8 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000024500=0x14000024500,sliceheader的地址0x1400000c048
[0 1 2 3 6 7 8 9 10]
長度:9 容量:16 底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x1400001e100=0x1400001e100,sliceheader的地址0x1400000c048
24
package main
import "fmt"
func main() {
// case1
m := []int64{2, 3}
fmt.Println("len of old m is ", len(m)) // 2
fmt.Println("cap of old m is ", cap(m)) // 2
fmt.Println("")
m = append(m, 4, 5, 6)
fmt.Println("len of m is ", len(m)) //5
fmt.Println("cap of m is ", cap(m)) //! 6 如果要的容量是原來容量的兩倍還要多, 那新的容量就是所要求的容量大?。?那為何是6而不是5?對于字符串和整型,表現(xiàn)不一樣;而且為何是6?)
fmt.Println()
fmt.Println("------")
// case2
n := []int64{2, 3}
fmt.Println("len of old n is ", len(n)) //2
fmt.Println("cap of old n is ", cap(n)) //2
fmt.Println("")
fmt.Printf("切片n為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", n, len(n), cap(n), n, &n[0], &n) // []int64{2, 3}, 2, 2, 地址a=地址a,地址x
n = append(n, 4)
fmt.Printf("切片n為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", n, len(n), cap(n), n, &n[0], &n) // []int64{2, 3, 4}, 3, 4(兩倍擴容), 地址b=地址b,地址x
n = append(n, 5)
fmt.Printf("切片n為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", n, len(n), cap(n), n, &n[0], &n) // []int64{2, 3, 4, 5}, 4, 4, 地址b=地址b,地址x
n = append(n, 6)
fmt.Printf("切片n為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致%p=%p,sliceheader的地址%p\n", n, len(n), cap(n), n, &n[0], &n) // []int64{2, 3, 4, 5, 6}, 5, 8(兩倍擴容), 地址c=地址c,地址x
fmt.Println()
fmt.Println("len of n is ", len(n)) //5
fmt.Println("cap of n is ", cap(n)) //! 8 如果要的容量沒有原來容量兩倍大, 那就擴充到原來容量的兩倍.
fmt.Println("------")
}
fmt.Println("cap of m is ", cap(m)) //! 6 如果要的容量是原來容量的兩倍還要多, 那新的容量就是所要求的容量大???(那為何是6而不是 這一步是為什么?
輸出:
len of old m is 2
cap of old m is 2
len of m is 5
cap of m is 6
------
len of old n is 2
cap of old n is 2
切片n為:[]int64{2, 3},長度為2,容量為2,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x140000200e0=0x140000200e0,sliceheader的地址0x1400000c048
切片n為:[]int64{2, 3, 4},長度為3,容量為4,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000028100=0x14000028100,sliceheader的地址0x1400000c048
切片n為:[]int64{2, 3, 4, 5},長度為4,容量為4,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000028100=0x14000028100,sliceheader的地址0x1400000c048
切片n為:[]int64{2, 3, 4, 5, 6},長度為5,容量為8,底層數(shù)組的內(nèi)存地址的兩種表示方式應(yīng)該一致0x14000024500=0x14000024500,sliceheader的地址0x1400000c048
len of n is 5
cap of n is 8
------
再如:
package main
import "fmt"
func main() {
a := []byte{1, 0}
fmt.Println("len of old a is ", len(a)) // 2
fmt.Println("cap of old a is ", cap(a)) // 2
fmt.Println("")
a = append(a, 1, 1, 1)
fmt.Println("len of a is ", len(a)) // 5
fmt.Println("cap of a is ", cap(a)) // 8
fmt.Println("------")
b := []int{23, 51}
fmt.Println("len of old b is ", len(b)) // 2
fmt.Println("cap of old b is ", cap(b)) // 2
fmt.Println("")
b = append(b, 4, 5, 6)
fmt.Println("len of b is ", len(b)) // 5
fmt.Println("cap of b is ", cap(b)) // 6
fmt.Println("------")
c := []int32{1, 23}
fmt.Println("len of old c is ", len(c)) // 2
fmt.Println("cap of old c is ", cap(c)) // 2
fmt.Println("")
c = append(c, 2, 5, 6)
fmt.Println("len of c is ", len(c)) // 5
fmt.Println("cap of c is ", cap(c)) // 6
fmt.Println("------")
type D struct {
age byte
name string
}
d := []D{
{1, "123"},
{2, "234"},
}
fmt.Println("len of old d is ", len(d)) // 2
fmt.Println("cap of old d is ", cap(d)) // 2
fmt.Println("")
d = append(d, D{4, "456"}, D{5, "567"}, D{6, "678"})
fmt.Println("len of d is ", len(d)) // 5
fmt.Println("cap of d is ", cap(d)) // 5
}
再次疑惑: 為什么不同類型的切片,append之后的len和cap不一樣?
package main
import "fmt"
func main() {
m := []int64{2, 3}
fmt.Println("len of old m is ", len(m)) // 2
fmt.Println("cap of old m is ", cap(m)) // 2
fmt.Println("")
m = append(m, 4, 5, 6)
fmt.Println("len of m is ", len(m)) // 5
fmt.Println("cap of m is ", cap(m)) // 5
fmt.Println()
fmt.Println("------")
n := []int64{2, 3}
fmt.Println("len of old n is ", len(n)) // 2
fmt.Println("cap of old n is ", cap(n)) // 2
fmt.Println("")
n = append(n, 4)
n = append(n, 5)
n = append(n, 6)
fmt.Println("len of n is ", len(n)) // 5
fmt.Println("cap of n is ", cap(n)) // 8
fmt.Println("------")
}
擴容相關(guān)的邏輯在 go/src/runtime/slice.go中的func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice
但更換版本試了下,和從1.18版本開始的cap策略變更沒關(guān)系
(用1.17和1.21運行,結(jié)果是一樣的)
和element size有關(guān),跟防止overflow以及memory alignment 。ele size 還會影響new cap
不在這里roundup 到tcmalloc的塊大小,其他內(nèi)存也是浪費的。

在此感謝cwx老哥 (https://github.com/cuiweixie)一起研究
// Specialize for common values of et.Size.
// For 1 we don't need any division/multiplication.
// For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
// For powers of 2, use a variable shift.什么意思
這段注釋解釋了針對常見的 et.Size 值進行特殊處理的原因。
在這段代碼中,et.Size 是一個表示大小的整數(shù)值。注釋中提到了三種常見的情況:
當(dāng)
et.Size為 1 時,不需要進行除法或乘法運算。這是因為在計算機中,將一個數(shù)左移一位相當(dāng)于乘以 2,右移一位相當(dāng)于除以 2。因此,對于大小為 1 的情況,可以直接使用移位操作來處理,避免了除法或乘法的開銷。當(dāng)
et.Size等于當(dāng)前架構(gòu)的指針大?。?code>goarch.PtrSize)時,編譯器會將除法或乘法運算優(yōu)化為一個常數(shù)的位移操作。這是因為指針大小通常是2的冪次方,所以可以通過移位來進行高效的除法或乘法運算。對于其他大小為2的冪次方的情況,使用一個可變的位移操作。這意味著將一個數(shù)左移或右移的位數(shù)是可變的,取決于
et.Size的具體值。這種處理方式仍然利用了位移操作的高效性。
總之,這段注釋是解釋了為什么針對不同的 et.Size 值采取了不同的優(yōu)化策略,以提高計算效率。這些優(yōu)化措施是為了充分利用位移操作和特定的數(shù)學(xué)性質(zhì),從而減少除法或乘法的開銷。
et.Size_不同,影響到最后cap的計算:如果是8字節(jié)的數(shù)據(jù)類型比如int,newcap = int(capmem / goarch.PtrSize); 如果是2的指數(shù)倍的,比如string(占16字節(jié)),newcap = int(capmem >> shift)
et.Size_ 即元素類型占用的內(nèi)存空間,常見的如 int32,存儲大小:4; int64,存儲大小:8; string,存儲大小:16 // string類型底層是一個指針(8字節(jié)),和一個長度字段(8字節(jié))
通過在源碼中添加print,大致捋清了脈絡(luò):
基于1.21版本,switch case有四個優(yōu)先級:
- 尺寸為1的(布爾值類型)
- 尺寸為8的(64位機器;32位的話為4,在此不考慮)如int64類型;
- 尺寸為2的指數(shù)倍的,如string類型
- default兜底
最后必然還和內(nèi)存分配有關(guān)系,多級 mheap,mcentral(類似于全局隊列),mcache(類似于本地隊列),mspan(各種尺寸的內(nèi)存各有一塊)
很多個級別,涉及到向下取整,有一部分內(nèi)存碎片
相關(guān)調(diào)試代碼:
func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice {
oldLen := newLen - num
if raceenabled {
callerpc := getcallerpc()
racereadrangepc(oldPtr, uintptr(oldLen*int(et.Size_)), callerpc, abi.FuncPCABIInternal(growslice))
}
if msanenabled {
msanread(oldPtr, uintptr(oldLen*int(et.Size_)))
}
if asanenabled {
asanread(oldPtr, uintptr(oldLen*int(et.Size_)))
}
if newLen < 0 {
panic(errorString("growslice: len out of range"))
}
if et.Size_ == 0 {
// append should not create a slice with nil pointer but non-zero len.
// We assume that append doesn't need to preserve oldPtr in this case.
return slice{unsafe.Pointer(&zerobase), newLen, newLen}
}
newcap := oldCap
doublecap := newcap + newcap
if newLen > doublecap {
newcap = newLen
} else {
const threshold = 256
if oldCap < threshold {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < newLen {
// Transition from growing 2x for small slices
// to growing 1.25x for large slices. This formula
// gives a smooth-ish transition between the two.
newcap += (newcap + 3*threshold) / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = newLen
}
}
}
println("爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為:", newcap, "\n")
println("爽哥調(diào)試-et.Size_值為:", et.Size_, "\n")
var overflow bool
var lenmem, newlenmem, capmem uintptr
// Specialize for common values of et.Size.
// For 1 we don't need any division/multiplication.
// For goarch.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
// For powers of 2, use a variable shift.
switch {
case et.Size_ == 1:
println("爽哥調(diào)試-走到了et.Size_ == 1這里\n")
lenmem = uintptr(oldLen)
newlenmem = uintptr(newLen)
capmem = roundupsize(uintptr(newcap))
println("爽哥調(diào)試-此時capmem值為:", capmem)
overflow = uintptr(newcap) > maxAlloc
newcap = int(capmem)
case et.Size_ == goarch.PtrSize:
println("爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里\n")
lenmem = uintptr(oldLen) * goarch.PtrSize
newlenmem = uintptr(newLen) * goarch.PtrSize
capmem = roundupsize(uintptr(newcap) * goarch.PtrSize)
println("爽哥調(diào)試-此時capmem值為:", capmem)
overflow = uintptr(newcap) > maxAlloc/goarch.PtrSize
newcap = int(capmem / goarch.PtrSize)
case isPowerOfTwo(et.Size_):
println("爽哥調(diào)試-走到了isPowerOfTwo(et.Size_)這里\n")
var shift uintptr
if goarch.PtrSize == 8 {
// Mask shift for better code generation.
shift = uintptr(sys.TrailingZeros64(uint64(et.Size_))) & 63
} else {
shift = uintptr(sys.TrailingZeros32(uint32(et.Size_))) & 31
}
lenmem = uintptr(oldLen) << shift
newlenmem = uintptr(newLen) << shift
capmem = roundupsize(uintptr(newcap) << shift)
println("爽哥調(diào)試-此時capmem值為:", capmem)
println("爽哥調(diào)試-此時shift值為:", shift)
overflow = uintptr(newcap) > (maxAlloc >> shift)
newcap = int(capmem >> shift)
capmem = uintptr(newcap) << shift
default:
println("爽哥調(diào)試-走到了default兜底邏輯這里\n")
lenmem = uintptr(oldLen) * et.Size_
newlenmem = uintptr(newLen) * et.Size_
capmem, overflow = math.MulUintptr(et.Size_, uintptr(newcap))
capmem = roundupsize(capmem)
println("爽哥調(diào)試-此時capmem值為:", capmem)
newcap = int(capmem / et.Size_)
capmem = uintptr(newcap) * et.Size_
}
println("爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為:", newcap, ", capmem值為:", capmem, "\n")
// The check of overflow in addition to capmem > maxAlloc is needed
// to prevent an overflow which can be used to trigger a segfault
// on 32bit architectures with this example program:
//
// type T [1<<27 + 1]int64
//
// var d T
// var s []T
//
// func main() {
// s = append(s, d, d, d, d)
// print(len(s), "\n")
// }
if overflow || capmem > maxAlloc {
panic(errorString("growslice: len out of range"))
}
var p unsafe.Pointer
if et.PtrBytes == 0 {
p = mallocgc(capmem, nil, false)
// The append() that calls growslice is going to overwrite from oldLen to newLen.
// Only clear the part that will not be overwritten.
// The reflect_growslice() that calls growslice will manually clear
// the region not cleared here.
memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
} else {
// Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
p = mallocgc(capmem, et, true)
if lenmem > 0 && writeBarrier.enabled {
// Only shade the pointers in oldPtr since we know the destination slice p
// only contains nil pointers because it has been cleared during alloc.
bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(oldPtr), lenmem-et.Size_+et.PtrBytes)
}
}
memmove(p, oldPtr, lenmem)
println("爽哥調(diào)試-最終的的newcap值為:", newcap, ", capmem值為:", capmem, "\n")
println("爽哥調(diào)試---------------------本輪擴容結(jié)束------------------\n")
return slice{p, newLen, newcap}
}
package main
import "fmt"
func main() {
println("~~~~~~~~~~開始進入用戶代碼:~~~~~~~~~~~~~~") // 前面Go底層會有很多調(diào)用到growslice的地方
s1 := []string{"北京", "上海", "深圳"}
println("~~~~~~~~~~aaaaaaaaaaaaa:~~~~~~~~~~~~~~")
fmt.Printf("len(s1):%d,cap(s1):%d\n", len(s1), cap(s1)) // 這步會有調(diào)用growslice的行為
println("~~~~~~~~~~bbbbbbbbbbbbbb:~~~~~~~~~~~~~~")
println()
println("================正式開始:===============")
s1 = append(s1, "廣州", "成都", "重慶", "石家莊", "保定", "邢臺", "張家口", "濟南")
println("~~~~~~~~~~cccccccccccccc:~~~~~~~~~~~~~~")
println("長度為:", len(s1), "容量為:", cap(s1))
println("~~~~~~~~~~dddddddddddddd:~~~~~~~~~~~~~~")
// fmt.Printf("len(s1):%d,cap(s1):%d\n", len(s1), cap(s1))
println("------")
s2 := []int{1, 2, 3}
//fmt.Printf("len(s2):%d,cap(s2):%d\n", len(s2), cap(s2))
println("長度為:", len(s2), "容量為:", cap(s2))
s2 = append(s2, 4, 5, 6, 7, 8, 9, 10, 11)
//fmt.Printf("len(s2):%d,cap(s2):%d\n", len(s2), cap(s2))
println("長度為:", len(s2), "容量為:", cap(s2))
}
輸出:
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 1
爽哥調(diào)試-et.Size_值為: 8
爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里
爽哥調(diào)試-此時capmem值為: 8
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 1 , capmem值為: 8
爽哥調(diào)試-最終的的newcap值為: 1 , capmem值為: 8
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 1
爽哥調(diào)試-et.Size_值為: 4
爽哥調(diào)試-走到了isPowerOfTwo(et.Size_)這里
爽哥調(diào)試-此時capmem值為: 8
爽哥調(diào)試-此時shift值為: 2
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 2 , capmem值為: 8
爽哥調(diào)試-最終的的newcap值為: 2 , capmem值為: 8
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 4
爽哥調(diào)試-et.Size_值為: 4
爽哥調(diào)試-走到了isPowerOfTwo(et.Size_)這里
爽哥調(diào)試-此時capmem值為: 16
爽哥調(diào)試-此時shift值為: 2
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 4 , capmem值為: 16
爽哥調(diào)試-最終的的newcap值為: 4 , capmem值為: 16
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 1
爽哥調(diào)試-et.Size_值為: 8
爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里
爽哥調(diào)試-此時capmem值為: 8
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 1 , capmem值為: 8
爽哥調(diào)試-最終的的newcap值為: 1 , capmem值為: 8
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 2
爽哥調(diào)試-et.Size_值為: 8
爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里
爽哥調(diào)試-此時capmem值為: 16
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 2 , capmem值為: 16
爽哥調(diào)試-最終的的newcap值為: 2 , capmem值為: 16
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 4
爽哥調(diào)試-et.Size_值為: 8
爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里
爽哥調(diào)試-此時capmem值為: 32
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 4 , capmem值為: 32
爽哥調(diào)試-最終的的newcap值為: 4 , capmem值為: 32
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 82
爽哥調(diào)試-et.Size_值為: 16
爽哥調(diào)試-走到了isPowerOfTwo(et.Size_)這里
爽哥調(diào)試-此時capmem值為: 1408
爽哥調(diào)試-此時shift值為: 4
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 88 , capmem值為: 1408
爽哥調(diào)試-最終的的newcap值為: 88 , capmem值為: 1408
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 8
爽哥調(diào)試-et.Size_值為: 8
爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里
爽哥調(diào)試-此時capmem值為: 64
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 8 , capmem值為: 64
爽哥調(diào)試-最終的的newcap值為: 8 , capmem值為: 64
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 1
爽哥調(diào)試-et.Size_值為: 16
爽哥調(diào)試-走到了isPowerOfTwo(et.Size_)這里
爽哥調(diào)試-此時capmem值為: 16
爽哥調(diào)試-此時shift值為: 4
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 1 , capmem值為: 16
爽哥調(diào)試-最終的的newcap值為: 1 , capmem值為: 16
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
~~~~~~~~~~開始進入用戶代碼:~~~~~~~~~~~~~~
~~~~~~~~~~aaaaaaaaaaaaa:~~~~~~~~~~~~~~
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 1
爽哥調(diào)試-et.Size_值為: 8
爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里
爽哥調(diào)試-此時capmem值為: 8
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 1 , capmem值為: 8
爽哥調(diào)試-最終的的newcap值為: 1 , capmem值為: 8
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 8
爽哥調(diào)試-et.Size_值為: 1
爽哥調(diào)試-走到了et.Size_ == 1這里
爽哥調(diào)試-此時capmem值為: 8
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 8 , capmem值為: 8
爽哥調(diào)試-最終的的newcap值為: 8 , capmem值為: 8
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 16
爽哥調(diào)試-et.Size_值為: 1
爽哥調(diào)試-走到了et.Size_ == 1這里
爽哥調(diào)試-此時capmem值為: 16
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 16 , capmem值為: 16
爽哥調(diào)試-最終的的newcap值為: 16 , capmem值為: 16
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 32
爽哥調(diào)試-et.Size_值為: 1
爽哥調(diào)試-走到了et.Size_ == 1這里
爽哥調(diào)試-此時capmem值為: 32
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 32 , capmem值為: 32
爽哥調(diào)試-最終的的newcap值為: 32 , capmem值為: 32
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
len(s1):3,cap(s1):3
~~~~~~~~~~bbbbbbbbbbbbbb:~~~~~~~~~~~~~~
================正式開始:===============
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 11
爽哥調(diào)試-et.Size_值為: 16
爽哥調(diào)試-走到了isPowerOfTwo(et.Size_)這里
爽哥調(diào)試-此時capmem值為: 176
爽哥調(diào)試-此時shift值為: 4
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 11 , capmem值為: 176
爽哥調(diào)試-最終的的newcap值為: 11 , capmem值為: 176
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
~~~~~~~~~~cccccccccccccc:~~~~~~~~~~~~~~
長度為: 11 容量為: 11
~~~~~~~~~~dddddddddddddd:~~~~~~~~~~~~~~
------
長度為: 3 容量為: 3
爽哥調(diào)試-未根據(jù)元素類型做處理前的newcap值為: 11
爽哥調(diào)試-et.Size_值為: 8
爽哥調(diào)試-走到了et.Size_ == goarch.PtrSize這里
爽哥調(diào)試-此時capmem值為: 96
爽哥調(diào)試-經(jīng)過一番邏輯處理后的newcap值為: 12 , capmem值為: 96
爽哥調(diào)試-最終的的newcap值為: 12 , capmem值為: 96
爽哥調(diào)試---------------------本輪擴容結(jié)束------------------
長度為: 11 容量為: 12
和 你真的了解go語言中的切片嗎? 最后 3.12 問題12差不多
case5: 初始容量的確定
通過
s := make([]int,10)這種方式,如果沒有指定cap的值,則默認(rèn)與len相同也可以顯式指定,可以很大,但不能比len小,否則會報
len larger than cap in make([]int)
package main
import "fmt"
func main() {
demo := make([]int, 9)
demo2 := demo
// []int{0,0,0,0,0,0,0,0,0}, 9, 9(而不是16!), 地址a, 地址x
fmt.Printf("切片demo為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p,sliceheader的地址為%p\n", demo, len(demo), cap(demo), demo, &demo)
// []int{0,0,0,0,0,0,0,0,0}, 9, 9, 地址a, 地址y
fmt.Printf("切片demo2為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p,sliceheader的地址為%p\n", demo2, len(demo2), cap(demo2), demo2, &demo2)
fmt.Println("-------")
demo3 := append(demo, 1)
// []int{0,0,0,0,0,0,0,0,0, 1}, 10, 18(為什么?!), 地址b(發(fā)生了擴容), 地址z
fmt.Printf("切片demo3為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p,sliceheader的地址為%p\n", demo3, len(demo3), cap(demo3), demo3, &demo3)
demo4 := append(demo3, 1, 2, 3)
// []int{0,0,0,0,0,0,0,0,0, 1,1,2,3}, 13, 18, 地址b(未擴容), 地址u
fmt.Printf("切片demo4為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p,sliceheader的地址為%p\n", demo4, len(demo4), cap(demo4), demo4, &demo4)
fmt.Println()
}
輸出:
切片demo為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0},長度為9,容量為9,底層數(shù)組的內(nèi)存地址為0x140000260f0,sliceheader的地址為0x1400000c048
切片demo2為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0},長度為9,容量為9,底層數(shù)組的內(nèi)存地址為0x140000260f0,sliceheader的地址為0x1400000c060
-------
切片demo3為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 1},長度為10,容量為18,底層數(shù)組的內(nèi)存地址為0x14000102000,sliceheader的地址為0x1400000c0d8
切片demo4為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3},長度為13,容量為18,底層數(shù)組的內(nèi)存地址為0x14000102000,sliceheader的地址為0x1400000c120
為什么初始容量為9?
為什么后面擴容是18而不是16?
通過 append 操作,可以在 slice 末尾,額外新增一個元素. 需要注意,這里的末尾指的是針對 slice 的長度 len 而言. 這個過程中倘若發(fā)現(xiàn) slice 的剩余容量已經(jīng)不足了,則會對 slice 進行擴容
當(dāng) slice 當(dāng)前的長度 len 與容量 cap 相等時,下一次 append 操作就會引發(fā)一次切片擴容
<font size=3 color="orange">
切片的擴容流程源碼位于 runtime/slice.go 文件的 growslice 方法當(dāng)中,其中核心步驟如下:
? 倘若擴容后預(yù)期的新容量小于原切片的容量,則 panic
? 倘若切片元素大小為 0(元素類型為 struct{}),則直接復(fù)用一個全局的 zerobase 實例,直接返回
? 倘若預(yù)期的新容量超過老容量的兩倍,則直接采用預(yù)期的新容量
? 倘若老容量小于 256,則直接采用老容量的2倍作為新容量
? 倘若老容量已經(jīng)大于等于 256,則在老容量的基礎(chǔ)上擴容 1/4 的比例并且累加上 192 的數(shù)值,持續(xù)這樣處理,直到得到的新容量已經(jīng)大于等于預(yù)期的新容量為止
? 結(jié)合 mallocgc 流程中,對內(nèi)存分配單元 mspan 的等級制度,推算得到實際需要申請的內(nèi)存空間大小
? 調(diào)用 mallocgc,對新切片進行內(nèi)存初始化
? 調(diào)用 memmove 方法,將老切片中的內(nèi)容拷貝到新切片中
? 返回擴容后的新切片
</font>
runtime/slice.go:
newcap := oldCap
doublecap := newcap + newcap
if newLen > doublecap {
newcap = newLen
} else {
const threshold = 256
if oldCap < threshold {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < newLen {
// Transition from growing 2x for small slices
// to growing 1.25x for large slices. This formula
// gives a smooth-ish transition between the two.
newcap += (newcap + 3*threshold) / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = newLen
}
}
}
newcap 經(jīng)過如上邏輯后,還要再根據(jù)元素類型,做一次處理。詳見case4中的源碼調(diào)試
package main
import "fmt"
//https://dashen.tech/2010/03/02/golang%E4%B9%8Bslice%E4%B8%AD%E7%9A%84%E5%B0%8Ftips/
// https://dashen.tech/2020/08/05/%E4%B8%A4%E4%B8%AAgolang%E5%B0%8F%E9%97%AE%E9%A2%98/
//https://dashen.tech/2021/03/01/%E4%B8%80%E4%B8%8D%E7%95%99%E7%A5%9E%E5%B0%B1%E6%8E%89%E5%9D%91/#map%E5%92%8Cslice%E5%8F%98%E9%87%8F%E7%9A%84%E8%B5%8B%E5%80%BC%E4%BD%9C%E7%94%A8%E8%8C%83%E5%9B%B4%E9%97%AE%E9%A2%98
// 特別留意append
func main() {
demo := make([]int, 10)
demo2 := demo
// []int{0,0,0,0,0,0,0,0,0,0}, 10, 10(而不是16?。? 地址a
fmt.Printf("切片demo為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", demo, len(demo), cap(demo), demo)
// []int{0,0,0,0,0,0,0,0,0,0}, 10, 10(而不是16!), 地址a
fmt.Printf("切片demo2為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", demo2, len(demo2), cap(demo2), demo2)
demo3 := append(demo, 1)
// []int{0,0,0,0,0,0,0,0,0,0,1}, 11, 20?(而不是16?。? 地址b(發(fā)生了擴容)
fmt.Printf("切片demo3為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", demo3, len(demo3), cap(demo3), demo3)
demo4 := append(demo3, 1, 2, 3)
// []int{0,0,0,0,0,0,0,0,0,0,1,1,2,3}, 14, 20(而不是16?。? 地址b(未發(fā)生擴容)
fmt.Printf("切片demo4為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", demo4, len(demo4), cap(demo4), demo4)
fmt.Println()
fmt.Println("---------------------")
// 對于這種sli2 = append(sli1,6,6,6),如果沒發(fā)生擴容,sli1和sli2底層數(shù)組一樣
// 對于sli1 = append(sli,6,6), sli2 = append(sli,8,8),即便容量一樣,sli1和sli2底層數(shù)組也不一樣..
//var sli []int
//sli := make([]int, 0)
sli := make([]int, 11)
// []int{0,0,0,0,0,0,0,0,0,0,0}, 11, 11(而不是16?。? 地址c
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
sli1 := append(sli, 1)
// []int{0,0,0,0,0,0,0,0,0,0,0,1}, 12, 22(而不是16!), 地址d(發(fā)生了擴容)
fmt.Printf("[sli1] %v len:%d cap:%d ptr:%p\n", sli1, len(sli1), cap(sli1), sli1)
fmt.Println()
// []int{0,0,0,0,0,0,0,0,0,0,0}, 11, 11(而不是16?。? 地址c
fmt.Printf("此時切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
// []int{0,0,0,0,0,0,0,0,0,0,0,1,2}, 13, 22(而不是26!), 地址e(發(fā)生了擴容)
sli2 := append(sli, 1, 2)
fmt.Printf("[sli2] %v len:%d cap:%d ptr:%p\n", sli2, len(sli2), cap(sli2), sli2)
fmt.Println()
// []int{0,0,0,0,0,0,0,0,0,0,0}, 11, 11(而不是16?。? 地址c
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
sli3 := append(sli, 1, 2, 3)
// []int{0,0,0,0,0,0,0,0,0,0,0,1,2,3}, 14, 22(而不是28!), 地址f(發(fā)生了擴容)
fmt.Printf("[sli3] %v len:%d cap:%d ptr:%p\n", sli3, len(sli3), cap(sli3), sli3)
fmt.Println()
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
sli4 := append(sli, 1, 2, 3, 4)
fmt.Printf("[sli4] %v len:%d cap:%d ptr:%p\n", sli4, len(sli4), cap(sli4), sli4)
fmt.Println()
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
sli5 := append(sli, 1, 2, 3, 4, 5)
fmt.Printf("[sli5] %v len:%d cap:%d ptr:%p\n", sli5, len(sli5), cap(sli5), sli5)
fmt.Println()
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
sli6 := append(sli, 1, 2, 3, 4, 5, 6)
fmt.Printf("[sli6] %v len:%d cap:%d ptr:%p\n", sli6, len(sli6), cap(sli6), sli6)
fmt.Println()
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
sli7 := append(sli, 1, 2, 3, 4, 5, 6, 7)
fmt.Printf("[sli7] %v len:%d cap:%d ptr:%p\n", sli7, len(sli7), cap(sli7), sli7)
fmt.Println()
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
sli8 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8)
fmt.Printf("[sli8] %v len:%d cap:%d ptr:%p\n", sli8, len(sli8), cap(sli8), sli8)
fmt.Println()
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
sli9 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9)
fmt.Printf("[sli9] %v len:%d cap:%d ptr:%p\n", sli9, len(sli9), cap(sli9), sli9)
fmt.Println()
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
sli10 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
fmt.Printf("[sli10] %v len:%d cap:%d ptr:%p\n", sli10, len(sli10), cap(sli10), sli10)
fmt.Println()
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
sli11 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
fmt.Printf("[sli11] %v len:%d cap:%d ptr:%p\n", sli11, len(sli11), cap(sli11), sli11)
fmt.Println()
// []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,地址c
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
sli12 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
// [sli12] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 10 11 12] len:23 cap:24 地址t
fmt.Printf("[sli12] %v len:%d cap:%d ptr:%p\n", sli12, len(sli12), cap(sli12), sli12)
fmt.Println()
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
sli13 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
fmt.Printf("[sli13] %v len:%d cap:%d ptr:%p\n", sli13, len(sli13), cap(sli13), sli13)
fmt.Println()
fmt.Printf("切片sli為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", sli, len(sli), cap(sli), sli)
}
輸出:
切片demo為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為10,容量為10,底層數(shù)組的內(nèi)存地址為0x140000ba000
切片demo2為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為10,容量為10,底層數(shù)組的內(nèi)存地址為0x140000ba000
切片demo3為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},長度為11,容量為20,底層數(shù)組的內(nèi)存地址為0x140000c2000
切片demo4為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3},長度為14,容量為20,底層數(shù)組的內(nèi)存地址為0x140000c2000
---------------------
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli1] [0 0 0 0 0 0 0 0 0 0 0 1] len:12 cap:22 ptr:0x140000c6000
此時切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli2] [0 0 0 0 0 0 0 0 0 0 0 1 2] len:13 cap:22 ptr:0x140000c60b0
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli3] [0 0 0 0 0 0 0 0 0 0 0 1 2 3] len:14 cap:22 ptr:0x140000c6160
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli4] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4] len:15 cap:22 ptr:0x140000c6210
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli5] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5] len:16 cap:22 ptr:0x140000c62c0
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli6] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6] len:17 cap:22 ptr:0x140000c6370
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli7] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7] len:18 cap:22 ptr:0x140000c6420
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli8] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8] len:19 cap:22 ptr:0x140000c64d0
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli9] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9] len:20 cap:22 ptr:0x140000c6580
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli10] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 10] len:21 cap:22 ptr:0x140000c6630
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli11] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 10 11] len:22 cap:22 ptr:0x140000c66e0
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli12] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 10 11 12] len:23 cap:24 ptr:0x140000c8000
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
[sli13] [0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 10 11 12 13] len:24 cap:24 ptr:0x140000c80c0
切片sli為:[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},長度為11,容量為11,底層數(shù)組的內(nèi)存地址為0x1400008e060
package main
import "fmt"
func main() {
var sli []int
sli1 := append(sli, 1)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli1, len(sli1), cap(sli1), sli1)
sli2 := append(sli, 1, 2)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli2, len(sli2), cap(sli2), sli2)
sli3 := append(sli, 1, 2, 3)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli3, len(sli3), cap(sli3), sli3)
sli4 := append(sli, 1, 2, 3, 4)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli4, len(sli4), cap(sli4), sli4)
sli5 := append(sli, 1, 2, 3, 4, 5)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli5, len(sli5), cap(sli5), sli5)
sli6 := append(sli, 1, 2, 3, 4, 5, 6)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli6, len(sli6), cap(sli6), sli6)
sli7 := append(sli, 1, 2, 3, 4, 5, 6, 7)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli7, len(sli7), cap(sli7), sli7)
sli8 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli8, len(sli8), cap(sli8), sli8)
sli9 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli9, len(sli9), cap(sli9), sli9)
sli10 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli10, len(sli10), cap(sli10), sli10)
sli11 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli11, len(sli11), cap(sli11), sli11)
sli12 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli12, len(sli12), cap(sli12), sli12)
sli13 := append(sli, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", sli13, len(sli13), cap(sli13), sli13)
}
輸出為:
[1] len:1 cap:1 ptr:0x140000200c8
[1 2] len:2 cap:2 ptr:0x140000200f0
[1 2 3] len:3 cap:3 ptr:0x1400001c0d8
[1 2 3 4] len:4 cap:4 ptr:0x14000028100
[1 2 3 4 5] len:5 cap:6 ptr:0x14000022270
[1 2 3 4 5 6] len:6 cap:6 ptr:0x140000222a0
[1 2 3 4 5 6 7] len:7 cap:8 ptr:0x14000024500
[1 2 3 4 5 6 7 8] len:8 cap:8 ptr:0x14000024540
[1 2 3 4 5 6 7 8 9] len:9 cap:10 ptr:0x140000260f0
[1 2 3 4 5 6 7 8 9 10] len:10 cap:10 ptr:0x14000026140
[1 2 3 4 5 6 7 8 9 10 11] len:11 cap:12 ptr:0x140000165a0
[1 2 3 4 5 6 7 8 9 10 11 12] len:12 cap:12 ptr:0x14000016600
[1 2 3 4 5 6 7 8 9 10 11 12 13] len:13 cap:14 ptr:0x1400001a230
兩種不同的聲明方式,對初始容量的影響

(圖片來自網(wǎng)絡(luò))
package main
import "fmt"
func create(iterations int) []int {
a := make([]int, 0)
for i := 0; i < iterations; i++ {
a = append(a, i)
}
return a
}
func main() {
sliceFromLoop()
fmt.Println("-----------------------")
sliceFromLiteral()
}
func sliceFromLoop() {
fmt.Printf("** NOT working as expected: **\n\n")
i := create(11)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},11,11(為什么??),地址b
fmt.Printf("切片i為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)
fmt.Println("initial slice: ", i)
fmt.Println()
j := append(i, 100)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100},12,22,地址c(發(fā)生了擴容)
fmt.Printf("切片j為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", j, len(j), cap(j), j)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},11,11,地址b
fmt.Printf("【原始切片i】為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)
fmt.Println()
g := append(i, 101)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 101},12,22,地址d(發(fā)生了擴容)
fmt.Printf("切片g為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", g, len(g), cap(g), g)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},11,11,地址b
fmt.Printf("【原始切片i】為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)
fmt.Println()
h := append(i, 102)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 102},12,22,地址f(發(fā)生了擴容)
fmt.Printf("切片h為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", h, len(h), cap(h), h)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},11,11,地址b
fmt.Printf("【原始切片i】為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)
fmt.Println()
fmt.Println("最后的結(jié)果:")
fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h) // 因為發(fā)生了擴容,j, g, h之間不會相互影響
}
func sliceFromLiteral() {
fmt.Printf("\n\n** working as expected: **\n")
i := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 11, 16, 地址a
fmt.Printf("切片i為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)
fmt.Println("initial slice: ", i)
fmt.Println()
j := append(i, 100)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100},12,16,地址a
fmt.Printf("切片j為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", j, len(j), cap(j), j)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 11, 16, 地址a
fmt.Printf("【原始切片i】為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)
fmt.Println()
g := append(i, 101)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 101}, 12, 16, 地址a
fmt.Printf("切片g為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", g, len(g), cap(g), g)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 11, 16, 地址a
fmt.Printf("【原始切片i】為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)
fmt.Println()
h := append(i, 102)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 102}, 12, 16, 地址a
fmt.Printf("切片h為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", h, len(h), cap(h), h)
// []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 11, 16, 地址a
fmt.Printf("【原始切片i】為:%#v,長度為%d,容量為%d,底層數(shù)組的內(nèi)存地址為%p\n", i, len(i), cap(i), i)
fmt.Println()
fmt.Println("最后的結(jié)果:")
fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h) // i, j, g, h共用一個底層數(shù)組,改值 會相互影響
}



番外: 與append無關(guān)的一些case:
迭代過程中修改切片的值
package main
func main() {
var s = []int{1, 2, 3}
for i, n := range s {
if i == 0 {
s[1], s[2] = 8, 9
}
print(n)
}
}
輸出: 189
并發(fā)寫入
package main
import (
"fmt"
)
func main() {
var sli []int
for i := 0; i < 10; i++ {
go func() {
for j := 0; j < 10; j++ {
sli = append(sli, 1)
}
}()
}
//time.Sleep(time.Microsecond) // 不加這一行,很可能是0或較小的數(shù);加上這行,也小于100
fmt.Println(len(sli))
}
加鎖后:
package main
import (
"fmt"
"sync"
"time"
)
var mu sync.Mutex
func main() {
var sli []int
for i := 0; i < 10; i++ {
go func() {
for j := 0; j < 10; j++ {
mu.Lock()
sli = append(sli, 1)
mu.Unlock()
}
}()
}
time.Sleep(time.Microsecond)
time.Sleep(5e9) // 不加這一行,結(jié)果一般小于100;100次加鎖解鎖操作,1ms內(nèi)完不成
fmt.Println(len(sli))
}
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var sli []int
var mu sync.Mutex
for i := 0; i < 10; i++ {
//var mu sync.Mutex
go func() {
// var mu sync.Mutex
for j := 0; j < 10; j++ {
// var mu sync.Mutex
mu.Lock()
sli = append(sli, 1)
mu.Unlock()
}
}()
}
time.Sleep(time.Microsecond)
time.Sleep(5e9) // 不加這一行,結(jié)果一般小于100;100次加鎖解鎖操作,1ms內(nèi)完不成
fmt.Println(len(sli))
}
把鎖初始化的操作放在循環(huán)內(nèi)是不行的,最后的結(jié)果一定小于100.
要放到全局,或者循環(huán)體外,只初始化一把鎖,而不是n把
interface 類型的切片可能出錯的點
package main
import (
"fmt"
)
func main() {
sli := []int64{1, 2, 3}
var sliIface []interface{}
for _, item := range sli {
sliIface = append(sliIface, item)
}
rs := InSliceIface(int64(2), sliIface) // 2 必須指定為int64類型,否則會當(dāng)成int,最終結(jié)果為false
fmt.Println(rs)
}
func InSliceIface(ele interface{}, sli []interface{}) bool {
for _, v := range sli {
if v == ele {
return true
}
}
return false
}
泛型切片
package main
import (
"fmt"
)
func main() {
sli := []float64{1, 2, 3.14}
rs := InSlice(3.14, sli)
fmt.Println(rs)
}
func InSlice[T int | int8 | int32 | int64 | float32 | float64 | string](ele T, sli []T) bool {
for _, v := range sli {
if v == ele {
return true
}
}
return false
}
本文由mdnice多平臺發(fā)布