切片是對數組的抽象,數組的長度不可改變,切片是長度可變的動態數組。在go語言中切片的使用是明顯高於數組的。golang
一、var 切片名 []type數組
var s []int
複製代碼
二、使用make()函數定義,make([]type,len)markdown
var s []int = make([]int 0)
複製代碼
三、指定容量make([]type, len, cap),cap爲可變參數app
var s []int = make([]int,3,4)
複製代碼
type slice struct {
array unsafe.Pointer
len int // 長度
cap int // 容量
}
複製代碼
容量是指底層數組的大小,長度指可使用的大小函數
容量的用處是在你用 append 擴展長度時,若是新的長度小於容量,不會更換底層數組,不然,go 會新申請一個底層數組,拷貝這邊的值過去,把原來的數組丟掉。也就是說,容量的用途是:在數據拷貝和內存申請的消耗與內存佔用之間提供一個權衡。oop
// 首先聲明一個長度爲3,容量爲4的slice
s :=make([]int,3,4)
// 記錄內存地址
fmt.Printf("%p\n",s) // 0xc0000b6000
// 當插入一個元素,slice的長度變爲4,因此內存地址不變
s = append(s,4)
fmt.Printf("%p\n",s) // 0xc0000b6000
// 再插入一個元素,slice的長度變爲5,已經大於聲明變量時限制的容量4,因此進行擴容,更換底層數組,內存地址就改變了
s = append(s,5)
fmt.Printf("%p\n",s) // 0xc0000ba000
s = append(s,6)
fmt.Printf("%p\n",s) // 0xc0000ba000
s = append(s,7)
fmt.Printf("%p\n",s) // 0xc0000ba000
s = append(s,8)
fmt.Printf("%p\n",s) // 0xc0000ba000
// 再次擴容
s = append(s,9)
fmt.Printf("%p\n",s) // 0xc0000bc000
複製代碼
slice操做會影響到底層數組的改變,頻繁的內存申請會佔用內存,因此在聲明slice時儘可能預估好容量的大小。slice的擴容規則可參考下面的擴容部分。ui
一、因爲slice的底層是數組指針,因此一些slice的copy後的操做須要特別注意spa
s := make([]int, 3)
s = append(s,4,5,6)
fmt.Println(s) // [0 0 0 4 5 6]
a := s
a[3] = 8
// a數據的改變會影響到s中的元素
fmt.Println(s) // [0 0 0 8 5 6]
複製代碼
二、slice能夠向後擴展,但不能夠向前擴展指針
s := make([]int, 3)
s = append(s,4,5,6)
fmt.Println(s) // [0 0 0 4 5 6]
a := s[1:4]
fmt.Println(a) // [0 0 4]
// 雖然a中沒有5 6這兩個元素,但同樣能夠取到
fmt.Println(a[0:5]) // [0 0 4 5 6]
// 這種取值是不行的
fmt.Println(a[4])
複製代碼
上面的a取了s三個元素,code
slice的cap是在用append擴展長度時,控制底層數組的存儲是否要發生改變。 下面是golang底層slice擴容時一些比較關鍵的代碼,但並非擴容的所有代碼
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.cap < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
複製代碼
上面代碼的意思就是
一、若是新的容量大於舊容量的2倍,則直接使用新的容量
s:=make([]int,3,4)
s = append(s,4,5,6,7,8,9)
fmt.Println(cap(s)) // 10
複製代碼
上面原本運行完,cap應該爲9,但實際結果爲10,實際上是go當元素被添加到切片時,當新容量爲奇數時,容量增長1
二、若是1不知足,老的容量小於1024,則新的容量直接等於舊容量的2倍
s:=make([]int,3,3)
s = append(s,4)
fmt.Println(cap(s)) // 6
複製代碼
三、若是1不知足,舊容量大於等1024,且舊容量小於須要的容量,則舊容量不停的*1.25,直到大於新的容量
s := make([]int,1024,1024)
// 舊容量1024
fmt.Println(cap(s)) // 1024
s = append(s,1)
// 擴容完 1024 * 1.25 = 1280
fmt.Println(cap(s)) // 1280
add := make([]int,255)
s = append(s,add...)
// 再添加255個元素正好達到1280,容量不變
fmt.Println(cap(s)) // 1280
// 神奇的事情發生了,再次擴容應該爲1280 * 1.25 = 1600,但實際爲1696
s = append(s,1)
fmt.Println(cap(s)) // 1696
複製代碼
最後一次擴容本應該爲1600,但實際爲1696,其實slice擴容源碼裏上面的代碼只是比較關鍵的一部分,後面還有有一些操做,感興趣的能夠深刻研究一下。