Q:你先簡單介紹一下自己吧
A:嘛哩嘛哩哄,嘛哩嘛哩哄,嘛哩嘛哩哄
Q:你說這麼多嘛哩嘛哩哄,那你瞭解陣列嗎?
A:陣列是具有相同型別且長度固定的資料結構,由於長度不可改變,在實際中使用較少,一般用切片來代替使用。
Q:你剛剛說到切片,切片和陣列有什麼相同點和不同點嗎?
A:相同點是他們都是隻能儲存相同型別的資料結構,切片的底層資料也是基於陣列的,不同點是切片是可以改變長度的,能自動擴容。
Q:嗯,那切片的底層是怎麼組成的呢,它的擴容規則你清楚嗎?
A:切片由三部分組成,分別是切片的長度,切片的容量,切片的資料(指向底層陣列的指標),至於擴容規則可就難說了。
Q:難說也是要說的,你搞快點。
A:切片擴容的基本規則是:當切片append的時候(會呼叫下面的函式),新切片的必須容量大於老切片容量的兩倍的,那麼新切片的容量就是新切片的必須容量,反之,如果老切片的長度小於1024,那麼新切片的容量就等於老切片的容量的兩倍,如果老切片大於等於1024,則新切片容量等於老切片容量的1.25倍,這是切片的預估容量。
// runtime/slice.go
func growslice(et *_type, old slice, cap int) slice {
//et 表示slice的一個元素,old 表示舊的slice cap 表示新切片必須的容量
...
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 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
}
}
}
...
}
根據以上規則 ,一下切片擴容必須的切片容量為 5 ,5 > 2*2,所以列印出來應該是len=5, cap=5
package main
import (
"fmt"
)
func main() {
s := []int{1, 2}
s = append(s, 4, 5, 6)
fmt.Printf("len=%d, cap=%d", len(s), cap(s))
}
但是實際列印卻是 len=5, cap=6
,原來容量計算完了後還要考慮到記憶體的高效利用,進行記憶體對齊,則會呼叫這個函式
func roundupsize(size uintptr) uintptr {
if size < _MaxSmallSize {
if size <= smallSizeMax-8 {
return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])
} else {
return uintptr(class_to_size[size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]])
}
}
if size+_PageSize < size {
return size
}
return alignUp(size, _PageSize)
}
size 表示新切片需要的記憶體大小 我們傳入的int 型別,每個佔用8位元組(可以呼叫unsafe.Sizeof()
函式檢視佔用的大小),一共5個 所以是40,size小於_MaxSmallSize 並且小於 smallSizeMax-8 ,那麼使用通用公式(size+smallSizeDiv-1)/smallSizeDiv
計算得到 5,
然後到size_to_class8
找到第五號元素 為 4
,再從 class_to_size
找到 第四號元素 為 48,這就是新切片佔用的記憶體大小,每個int 佔用 8 位元組,所以最終切片的容量為 6 。所以說切片的擴容有它基本的擴容規則,在規則之後還要考慮記憶體對齊,這就代表不同資料型別的切片擴容的容量大小是會不一致。
Q:好,那我們進入下一個問題
本作品採用《CC 協議》,轉載必須註明作者和本文連結