Go 切片相關(guān)面試題
來(lái)自軒脈刃的刀光劍影
這篇是關(guān)于go切片的一些問(wèn)題和回答。
go的切片基本上是代碼中使用最多的一種數(shù)據(jù)結(jié)構(gòu)了,使用這種數(shù)據(jù)結(jié)構(gòu)有哪些要注意的點(diǎn),這個(gè)是非常必要了解的東西。基本上,以前寫的一篇博客 https://www.cnblogs.com/yjf512/p/9531282.html ?就說(shuō)的很清楚了。這里再深挖一些。
問(wèn)題:go的切片數(shù)據(jù)結(jié)構(gòu)是什么樣子的?
切片是有可能在編譯器就被內(nèi)聯(lián)的,而如果在編譯器沒(méi)有被內(nèi)聯(lián),進(jìn)入運(yùn)行期,就是直接使用SliceHeader數(shù)據(jù)結(jié)構(gòu)。
type?SliceHeader?struct?{
?Data?uintptr
?Len??int
?Cap??int
}
這三個(gè)字段分別表示指針,長(zhǎng)度,容量。
問(wèn)題:為什么在初始化slice的時(shí)候盡量補(bǔ)全cap
當(dāng)我們要?jiǎng)?chuàng)建一個(gè)slice結(jié)構(gòu),并且往slice中append元素的時(shí)候,我們可能有兩種寫法來(lái)初始化這個(gè)slice。
方法1:
package?main
import?"fmt"
func?main()?{
?arr?:=?[]int{}
?arr?=?append(arr,?1,2,3,4,?5)
?fmt.Println(arr)
}
方法2:
package?main
import?"fmt"
func?main()?{
???arr?:=?make([]int,?0,?5)
???arr?=?append(arr,?1,2,3,4,?5)
???fmt.Println(arr)
}
方法2相較于方法1,就只有一個(gè)區(qū)別:在初始化[]int slice的時(shí)候在make中設(shè)置了cap的長(zhǎng)度,就是slice的大小。
這兩種方法對(duì)應(yīng)的功能和輸出結(jié)果是沒(méi)有任何差別的,但是實(shí)際運(yùn)行的時(shí)候,方法2會(huì)比少運(yùn)行了一個(gè)growslice的命令。
這個(gè)我們可以通過(guò)打印匯編碼進(jìn)行查看:
方法1:
image-20211219173237557方法2:
image-20211219174112164我們看到方法1中使用了growsslice方法,而方法2中是沒(méi)有調(diào)用這個(gè)方法的。
這個(gè)growslice的作用就是擴(kuò)充slice的容量大小。就好比是原先我們沒(méi)有定制容量,系統(tǒng)給了我們一個(gè)能裝兩個(gè)鞋子的盒子,但是當(dāng)我們裝到第三個(gè)鞋子的時(shí)候,這個(gè)盒子就不夠了,我們就要換一個(gè)盒子,而換這個(gè)盒子,我們勢(shì)必還需要將原先的盒子里面的鞋子也拿出來(lái)放到新的盒子里面。所以這個(gè)growsslice的操作是一個(gè)比較復(fù)雜的操作,它的表現(xiàn)和復(fù)雜度會(huì)高于最基本的初始化make方法。對(duì)追求性能的程序來(lái)說(shuō),應(yīng)該能避免盡量避免。
具體對(duì)growsslice函數(shù)具體實(shí)現(xiàn)同學(xué)有興趣的可以參考源碼src的 runtime/slice.go 。
當(dāng)然,我們并不是每次都能在slice初始化的時(shí)候就能準(zhǔn)確預(yù)估到最終的使用容量的。所以這里使用了一個(gè)“盡量”。明白是否設(shè)置slice容量的區(qū)別,我們?cè)谀茴A(yù)估容量的時(shí)候,請(qǐng)盡量使用方法2那種預(yù)估容量后的slice初始化方式。
問(wèn)題:如果不設(shè)置cap,make slice的時(shí)候,創(chuàng)建的cap為多大?
如果不設(shè)置cap,不管是使用make,還是直接使用[]slice 進(jìn)行初始化,編譯器都會(huì)計(jì)算初始化所需的空間,使用最小化的cap進(jìn)行初始化。
a?:=?make([]int,?0)??//?cap?為0
a?:=?[]int{1,2,3}?//?cap?為3
可以從ssa看出
image-20211221095655104問(wèn)題:slice什么時(shí)候決定擴(kuò)張?
之前寫過(guò)一篇文章 https://www.cnblogs.com/yjf512/p/10714792.html 里面得出的結(jié)論就是slice在編譯期就決定是否要調(diào)用growslice。
這個(gè)邏輯是正確的。
編譯器在ssa的時(shí)候 對(duì)于append是會(huì)轉(zhuǎn)換為 OAPPEND(cmd/compile/internal/typecheck/universe.go) 。而在 cmd/compile/internal/ssagen/ssa.go 中,對(duì)其進(jìn)行判斷。
image-20211221101614497目前還看不懂下面append下面的邏輯,不過(guò)基于這個(gè)注釋,能了解到這里growslice的邏輯。比較擴(kuò)容前后大小,如果原先cap小于擴(kuò)容后需要cap,就growslice。
總結(jié)
琢磨了四個(gè)關(guān)于切片的問(wèn)題:
問(wèn)題:go的切片數(shù)據(jù)結(jié)構(gòu)是什么樣子的?
問(wèn)題:為什么在初始化slice的時(shí)候盡量補(bǔ)全cap?
問(wèn)題:如果不設(shè)置cap,make slice的時(shí)候,創(chuàng)建的cap為多大?
問(wèn)題:slice什么時(shí)候決定擴(kuò)張?
感覺(jué)第四個(gè)問(wèn)題還沒(méi)想透。
推薦閱讀
我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù)?ebook?獲??;還可以回復(fù)「進(jìn)群」,和數(shù)萬(wàn) Gopher 交流學(xué)習(xí)。
