1. slice 擴(kuò)容機(jī)制
如果 Slice 要擴(kuò)容的容量大于 2 倍當(dāng)前的容量恕曲,則直接按想要擴(kuò)容的容量來 new 一個(gè)新的 Slice杂彭,否則繼續(xù)判斷當(dāng)前的長度 len岩喷,如果 len 小于 1024视事,則直接按 2 倍容量來擴(kuò)容匈勋,否則一直循環(huán)新增 1/4礼旅,直到大于想要擴(kuò)容的容量
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
for newcap < cap {
newcap += newcap / 4
}
}
}
實(shí)際使用
// =========== 第一種
a := make([]string, 5)
fmt.Println(len(a), cap(a)) // 輸出5 5
a = append(a, "aaa")
fmt.Println(len(a), cap(a)) // 輸出6 10
// 總結(jié): 由于make([]string, 5) 則默認(rèn)會(huì)初始化5個(gè) 空的"", 因此后面 append 時(shí),則需要2倍了
// =========== 第二種
a:=[]string{}
fmt.Println(len(a), cap(a)) // 輸出0 0
a = append(a, "aaa")
fmt.Println(len(a), cap(a)) // 輸出1 1
// 總結(jié):由于[]string{}, 沒有其他元素洽洁, 所以append 按 需要擴(kuò)容的 cap 來
// =========== 第三種
a := make([]string, 0, 5)
fmt.Println(len(a), cap(a)) // 輸出0 5
a = append(a, "aaa")
fmt.Println(len(a), cap(a)) // 輸出1 5
// 總結(jié):注意和第一種的區(qū)別痘系,這里不會(huì)默認(rèn)初始化5個(gè),所以后面的append容量是夠的饿自,不用擴(kuò)容
// =========== 第四種
b := make([]int, 1, 3)
a := []int{1, 2, 3}
copy(b, a)
fmt.Println(len(b)) // 輸出1
// 總結(jié):copy 取決于較短 slice 的 len, 一旦最小的len結(jié)束了汰翠,也就不再復(fù)制了
2.slice range 相關(guān)分析
下面代碼段 是否陷入死循環(huán)?
v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}
上面代碼的執(zhí)行是不會(huì)一直循環(huán)下去的昭雌,原因在于 range 的時(shí)候會(huì) copy 這個(gè) slice 上的 len 屬性到一個(gè)新的變量上复唤,然后根據(jù)這個(gè) copy 值去遍歷 slice,因此遍歷期間即使 slice 添加了元素城豁,也不會(huì)改變這個(gè)變量的值了苟穆。
參考 range slice 源代碼
// The loop we generate:
// for_temp := range
// len_temp := len(for_temp)
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = for_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
map range 的情況時(shí)如何?
m1 := make(map[string]string, 0)
m1["name"] = "jack"
for k, v := range m1 {
log.Println(k, v)
m1[fmt.Sprintf("%v_%v", k, 1)] = v
}
這段代碼代碼輸出結(jié)果是不固定的唱星,原因是 map實(shí)現(xiàn)是基于hash 表實(shí)現(xiàn)的雳旅,插入數(shù)據(jù)的位置是不固定的,遍歷過程中不保證一定遍歷到新插入的數(shù)據(jù)
map range 源碼:
// var hiter map_iteration_struct
// for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) {
// index_temp = *hiter.key
// value_temp = *hiter.val
// index = index_temp
// value = value_temp
// original body
// }
3. 注意事項(xiàng)
range 一個(gè) slice 的時(shí)候是進(jìn)行一個(gè)值拷貝的间聊,如果 slice 里存儲(chǔ)的是指針集合攒盈,那在 遍歷里修改是有效的,如果 slice 存儲(chǔ)的是值類型的集合哎榴,那么就是在 copy 它們的副本型豁,期間的修改也只是在修改這個(gè)副本僵蛛,跟原來的 slice 里的元素是沒有關(guān)系的
如果 slice 作為函數(shù)的入?yún)ⅲǔOM麑?duì) slice 的操作可以影響到底層數(shù)據(jù)迎变,但是如果在函數(shù)內(nèi)部 append 數(shù)據(jù)超過了 cap充尉,導(dǎo)致重新分配底層數(shù)組,這時(shí)修改的 slice 將不再是原來入?yún)⒌哪莻€(gè) slice 了衣形。因此通常不建議在函數(shù)內(nèi)部對(duì) slice 有 append 操作驼侠,若有需要?jiǎng)t顯示的 return 這個(gè) slice
func main() {
sl := make([]*M, 0, 0)
sl = append(sl, &M{
Name: "sun",
})
log.Println("sl 原始數(shù)據(jù):", len(sl), cap(sl))
s2 := add(sl)
log.Println("sl 處理后數(shù)據(jù):", len(sl), cap(sl))
log.Println("s2 處理后數(shù)據(jù)", len(s2), cap(s2))
for _, v := range s2 {
log.Println(v)
}
}
func add(m []*M) []*M {
m = append(m, &M{
Name: "wang1",
}, &M{
Name: "wang2",
})
return m
}
輸出:
2022/02/17 15:43:46 sl 原始數(shù)據(jù): 1 1
2022/02/17 15:43:46 sl 處理后數(shù)據(jù): 1 1
2022/02/17 15:43:46 s2 處理后數(shù)據(jù) 3 3
2022/02/17 15:43:46 &{sun}
2022/02/17 15:43:46 &{wang1}
2022/02/17 15:43:46 &{wang2}