go語言聖經中的解釋:
數組和slice之間有着緊密的聯系。
一個slice是一個輕量級的數據結構,提供了訪問數組子序列(或者全部)元素的功能,而且slice的底層確實引用一個數組對象。
一個slice由三個部分構成:指針、長度和容量。
指針指向第一個slice元素對應的底層數組元素的地址,要注意的是slice的第一個元素並不一定就是數組的第一個元素。
長度對應slice中元素的數目;長度不能超過容量
容量一般是從slice的開始位置到底層數據的結尾位置。內置的len和cap函數分別返回slice的長度和容量。
make([]T, len, cap) len<=cap
在底層,make創建了一個匿名的數組變量,然后返回一個slice
slice只引用了底層數組的前len個元素,但是容量將包含整個的數組。額外的元素是留給未來的增長用的
從下面這個自定義函數里可以了解擴容機制:
func appendInt(x []int, y int) []int { var z []int //+1個元素后新的長度 zlen := len(x) + 1 //長度小於原來的容量大小 if zlen <= cap(x) { // 還有剩余空間,直接可以拿到給新的slice z = x[:zlen] } else { // 沒有足夠的空間,給新的slice分配原來二倍的空間 zcap := zlen if zcap < 2*len(x) { zcap = 2 * len(x) } z = make([]int, zlen, zcap) copy(z, x) //把舊的slice復制到新的slice } z[len(x)] = y return z }
通過在每次擴展數組時直接將長度翻倍從而避免了多次內存分配,也確保了添加單個元素操的平均時間是一個常數時間
func main() { var x, y []int for i := 0; i < 10; i++ { y = appendInt(x, i) fmt.Printf("%d cap=%d\t%v\n", i, cap(y), y) x = y } } 每一次容量的變化都會導致重新分配內存和copy操作,當容量不夠的時候,會有一個翻倍擴充 0 cap=1 [0] 1 cap=2 [0 1] 2 cap=4 [0 1 2] 3 cap=4 [0 1 2 3] 4 cap=8 [0 1 2 3 4] 5 cap=8 [0 1 2 3 4 5] 6 cap=8 [0 1 2 3 4 5 6] 7 cap=8 [0 1 2 3 4 5 6 7] 8 cap=16 [0 1 2 3 4 5 6 7 8] 9 cap=16 [0 1 2 3 4 5 6 7 8 9]
向切片新增一個元素時,若該切片容量已滿,會首先根據切片容量進行判斷,小於1024字節擴容為原有容量的2倍,大於1024字節擴容為原有容量的1.25倍