初探Golang數(shù)據(jù)結(jié)構(gòu)之Slice的使用
在閱讀Go語(yǔ)言圣經(jīng)時(shí),一直對(duì)數(shù)組和切片的使用場(chǎng)景好奇,不明白為什么推薦使用切片來(lái)代替數(shù)組。希望能通過(guò)一些梳理,能更好的理解切片和數(shù)組,找到他們合適的使用場(chǎng)景。
切片與數(shù)組
關(guān)于切片和數(shù)組怎么選擇,我們來(lái)討論下這個(gè)問(wèn)題。
在Go中,數(shù)組是值類型,賦值和函數(shù)傳參都會(huì)復(fù)制整個(gè)數(shù)組數(shù)據(jù)。
func main() {
a := [2]int{100, 200}
// 賦值
var b = a
fmt.Printf("a : %p , %v\n", &a, a)
fmt.Printf("b : %p , %v\n", &b, b)
// 函數(shù)傳參
f(a)
f(b)
}
func f(array [2]int) {
fmt.Printf("array : %p , %v\n", &array, array)
}輸出結(jié)果:
a : 0xc0000180a0 , [100 200]
b : 0xc0000180b0 , [100 200]
array : 0xc0000180f0 , [100 200]
array : 0xc000018110 , [100 200]
可以看到,四個(gè)內(nèi)存地址都不相同,印證了前面的說(shuō)法。當(dāng)數(shù)組數(shù)據(jù)量達(dá)到百萬(wàn)級(jí)別時(shí),復(fù)制數(shù)組會(huì)給內(nèi)存帶來(lái)巨大的壓力,那能否通過(guò)傳遞指針來(lái)解決呢?
func main() {
a := [1]int{100}
f1(&a)
fmt.Printf("array : %p , %v\n", &a, a)
}
func f1(p *[1]int) {
fmt.Printf("f1 array : %p , %v\n", p, *p)
(*p)[0] += 100
}輸出結(jié)果:
f1 array : 0xc0000b0008 , [100]
array : 0xc0000b0008 , [200]
可以看到,數(shù)組指針可以實(shí)現(xiàn)我們想要的效果,解決了復(fù)制數(shù)組帶來(lái)的內(nèi)存問(wèn)題,不過(guò)函數(shù)接收的指針來(lái)自值拷貝,相對(duì)來(lái)說(shuō)沒(méi)有切片那么靈活。
func main() {
a := [1]int{100}
f1(&a)
// 切片
b := a[:]
f2(&b)
fmt.Printf("array : %p , %v\n", &a, a)
}
func f1(p *[1]int) {
fmt.Printf("f1 array : %p , %v\n", p, *p)
(*p)[0] += 100
}
func f2(p *[]int) {
fmt.Printf("f2 array : %p , %v\n", p, *p)
(*p)[0] += 100
}
//輸出結(jié)果
f1 array : 0xc000018098 , [100]
f2 array : 0xc00000c030 , [200]
array : 0xc000018098 , [300]可以看到,切片的指針和原來(lái)數(shù)組的指針是不同的。
總結(jié)
通常來(lái)說(shuō),使用數(shù)組進(jìn)行參數(shù)傳遞會(huì)消耗較多內(nèi)存,采用切片可以避免此問(wèn)題。切片是引用傳遞,不會(huì)占用較多內(nèi)存,效率更高一些。
切片的數(shù)據(jù)結(jié)構(gòu)
切片在編譯期是 cmd/compile/internal/types/type.go 包下的Slice類型,而它的運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu)位于 reflect.SliceHeader
type SliceHeader struct {
Data uintptr // 指向數(shù)組的指針
Len int // 當(dāng)前切片的長(zhǎng)度
Cap int // 當(dāng)前切片的容量,cap 總是 >= len
}
// 占用24個(gè)字節(jié)
fmt.Println(unsafe.Sizeof(reflect.SliceHeader{}))切片是對(duì)數(shù)組一個(gè)連續(xù)片段的引用,這個(gè)片段可以是整個(gè)數(shù)組,也可以是數(shù)組的一部分。切片的長(zhǎng)度可以在運(yùn)行時(shí)修改,最小為0,最大為關(guān)聯(lián)數(shù)組的長(zhǎng)度,切片是一個(gè)長(zhǎng)度可變的動(dòng)態(tài)窗口。
創(chuàng)建切片
使用make
slice := make([]int, 4, 6)
內(nèi)存空間申請(qǐng)了6個(gè)int類型的內(nèi)存大小。由于len=4,所以后面2個(gè)空間暫時(shí)無(wú)法訪問(wèn)到,但是容量是存在的。此時(shí)數(shù)組里每個(gè)變量都=0。
字面量
slice := []int{0, 1, 2}nil切片和空切片
// nil 切片
var s []int
// 空切片
s2 := make([]int, 0)
s3 := []int{}空切片和 nil 切片的區(qū)別在于,空切片指向的地址不是nil,指向的是一個(gè)內(nèi)存地址,但是它沒(méi)有分配任何內(nèi)存空間,即底層元素包含0個(gè)元素。
func main() {
var s []int
s2 := []int{}
s3 := make([]int, 0)
fmt.Println(s == nil)
fmt.Println(s2 == nil)
fmt.Println(s3 == nil)
}
// 輸出結(jié)果
true
false
false簡(jiǎn)單說(shuō),nil切片指針值為nil;而空切片的指針值不為nil,
需要說(shuō)明的一點(diǎn)是,不管是使用 nil 切片還是空切片,對(duì)其調(diào)用內(nèi)置函數(shù) append,len 和 cap 的效果都是一樣的。
但使用append時(shí)要注意:
- 如果append追加的數(shù)據(jù)長(zhǎng)度小于等于cap-len,只做一次數(shù)據(jù)拷貝。
- 如果append追加的數(shù)據(jù)長(zhǎng)度大于cap-len,則會(huì)分配一塊更大的內(nèi)存,然后把原數(shù)據(jù)拷貝過(guò)來(lái),再進(jìn)行追加。
特別當(dāng)我們需要構(gòu)建一個(gè)切片,是從原有切片復(fù)制而來(lái)時(shí),要注意值覆蓋問(wèn)題。
func main() {
s1 := []int{0, 1, 2, 3} // 先定義一個(gè)現(xiàn)有切片
s2 := s1[0:1] // 復(fù)制現(xiàn)有的切片
s2 = append(s2, 100)
fmt.Print(s1)
}輸出結(jié)果
[0 100 2 3]
原因還是切片本質(zhì)上是數(shù)組的一個(gè)動(dòng)態(tài)窗口,當(dāng)cap夠用時(shí),不會(huì)新開(kāi)辟內(nèi)存空間進(jìn)行復(fù)制,此時(shí)對(duì)數(shù)組的任何修改,都會(huì)對(duì)其他代理此數(shù)組的切片產(chǎn)生連帶影響。
到此這篇關(guān)于初探Golang數(shù)據(jù)結(jié)構(gòu)之Slice的使用的文章就介紹到這了,更多相關(guān)Go Slice內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go項(xiàng)目配置管理神器之viper的介紹與使用詳解
viper是一個(gè)完整的?Go應(yīng)用程序的配置解決方案,它被設(shè)計(jì)為在應(yīng)用程序中工作,并能處理所有類型的配置需求和格式,下面這篇文章主要給大家介紹了關(guān)于Go項(xiàng)目配置管理神器之viper的介紹與使用,需要的朋友可以參考下2023-02-02
go 類型轉(zhuǎn)換方式(interface 類型的轉(zhuǎn)換)
這篇文章主要介紹了go 類型轉(zhuǎn)換方式(interface 類型的轉(zhuǎn)換),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05
Go文件操作(新建打開(kāi)寫(xiě)入讀取刪除關(guān)閉)學(xué)習(xí)筆記
這篇文章主要為大家介紹了Go文件操作(新建打開(kāi)寫(xiě)入讀取刪除關(guān)閉)學(xué)習(xí)筆記,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
go編譯so庫(kù)讓python引用編譯后沒(méi)有.h文件的問(wèn)題
有時(shí)python需要引用go的一些開(kāi)源庫(kù),這時(shí)就需要go編譯成python可調(diào)用的庫(kù),本文給大家介紹了go編譯so庫(kù)讓python引用,編譯后沒(méi)有.h文件的問(wèn)題,需要的朋友可以參考下2024-02-02
go語(yǔ)言實(shí)現(xiàn)并發(fā)網(wǎng)絡(luò)爬蟲(chóng)的示例代碼
本文主要介紹了go語(yǔ)言實(shí)現(xiàn)并發(fā)網(wǎng)絡(luò)爬蟲(chóng)的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03

