golang slice介紹及擴容規則

切片slice

切片是對數組的抽象,數組的長度不可改變,切片是長度可變的動態數組。在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擴容源碼裏上面的代碼只是比較關鍵的一部分,後面還有有一些操做,感興趣的能夠深刻研究一下。

相關文章
相關標籤/搜索