參考鏈接: https://github.com/lvgithub/go_blog/blob/master/Books/slice.md
介紹
slice 是對數(shù)組的抽象,是對array的擴展,array的長度不可變,在特定場景中不太適用
slice 主要特點是不需要為它的容量擔心,可以追加元素,在追加時可能使切片的容量增大
slice 擴容
s := []int{1,2,3,4,5,6}
s = append(s, 6)
- 如果新的slice大小是當前大小2倍以上,則大小增長為新大小
- 如果當前slice cap 小于1024,按每次2倍增長,否則每次按當前大小1/4增長。直到增長的大小超過或等于新大小
- append的實現(xiàn)是在內(nèi)存中將slice的array值賦值到新申請的array
性能
通過上面我們知道slice的擴容涉及到內(nèi)存的拷貝,這樣帶來的好處是數(shù)據(jù)存儲在連續(xù)內(nèi)存上,比隨機訪問快很多,最直接的性能提升就是緩存命中率會高很多,這也就是為什么slice不采用動態(tài)鏈表實現(xiàn)的原因吧
我們知道拷貝內(nèi)存數(shù)據(jù)是有開銷的, 而其中最大的開銷不在 memmove 數(shù)據(jù)上,而是在開辟一塊新內(nèi)存malloc及之后的GC壓力
拷貝連續(xù)內(nèi)存是很快的,隨著cap變大,拷貝總成本還是 O(N) ,只是常數(shù)大了
假如不想發(fā)生拷貝,那你就沒有連續(xù)內(nèi)存。此時隨機訪問開銷會是:鏈表 O(N)
能知道所需的最大空間時,在make的時候預留相應(yīng)的 cap 就好
如果需要的空間很大,且每次都不確定,那就要在浪費內(nèi)存和耗 CPU 在 malloc + gc 上做權(quán)衡
鏈表的查找操作是從第一個元素開始,所以相對數(shù)組要耗時間的多,因為采用這樣的結(jié)構(gòu)對讀的性能有很大的提高
選擇
slice很靈活,大部分情況都能表現(xiàn)的很好
slice的容量超大并且需要頻繁的更改slice的內(nèi)容時,改用list更合適
舉例
s := []byte{1, 23, 4, 5, 67, 7}
s1 := s[2:3]
s1[0] = 100
fmt.Printf("s:%+v\n", s)
fmt.Printf("s[2] address is: %p\n", &s[2])
fmt.Printf("s1[1] address is: %p\n", &s1[0])
// s:[1 23 100 5 67 7]
// s[2] address is: 0xc00007e004
// s1[1] address is: 0xc00007e004
沒錯,slice s 第三位的值4被替換為了100,這是因為slice s1 的底層array指針指向 slice s 的第三位,因此操作s1會影響切片s,因此賦值切片需要使用如下辦法:
temp := copy(dst, src)
- 底層數(shù)組是可以被多個 slice 同時指向的
- 基于slice 創(chuàng)建新 slice 對象,新、老 slice 共用底層數(shù)組,對底層數(shù)組的更改都會影響到彼此。
- append可以掰斷新老slice共用底層數(shù)組的關(guān)系