Go語言中一定要知道的切片使用注意事項(xiàng)總結(jié)
1. 引言
在之前我寫了一篇 切片比數(shù)組好用在哪 的文章,仔細(xì)介紹了切片相比于數(shù)組的優(yōu)點(diǎn)。但切片事實(shí)上也隱藏著一些潛在的陷阱和需要注意的細(xì)節(jié),了解和掌握切片的使用注意事項(xiàng),可以避免意外的程序行為。本文將深入探討Go語言切片常見的注意事項(xiàng),從而能夠更好得使用切片。
2. 注意事項(xiàng)
2.1 注意一個(gè)數(shù)組可以同時(shí)被多個(gè)切片引用
當(dāng)創(chuàng)建一個(gè)切片時(shí),它實(shí)際上是對一個(gè)底層數(shù)組的引用。這意味著對切片的修改會直接影響到底層數(shù)組以及其他引用該數(shù)組的切片。這種引用關(guān)系可能導(dǎo)致一些意想不到的結(jié)果,下面是一個(gè)示例代碼來說明這個(gè)問題:
package main import "fmt" func main() { array := [5]int{1, 2, 3, 4, 5} firstSlice := array[1:4] // 創(chuàng)建一個(gè)切片,引用了底層數(shù)組的索引1到3的元素 secondSlice := array[1:3] fmt.Println("Original array:", firstSlice) // 輸出第一個(gè)切片 [2 3 4] fmt.Println("Original slice:", secondSlice) // 輸出第二個(gè)切片 [2 3] // 修改切片的第一個(gè)元素 firstSlice[0] = 10 fmt.Println("Modified array:", firstSlice) // 輸出第一個(gè)切片 [10 3 4] fmt.Println("Modified slice:", secondSlice) // 輸出第二個(gè)切片 [10 3] }
在上述代碼中,我們創(chuàng)建了一個(gè)長度為5的數(shù)組array
和兩個(gè)引用該數(shù)組的切片firstSlice
和secondSlice
。當(dāng)我們修改第一個(gè)切片的第一個(gè)元素為10時(shí),底層數(shù)組的對應(yīng)位置的元素也被修改了。這里導(dǎo)致了數(shù)組和其他引用該數(shù)組的切片的內(nèi)容也會受到影響。
如果我們有多個(gè)切片同時(shí)引用了同一個(gè)底層數(shù)組,同時(shí)我們并不想由于對某個(gè)切片的修改,影響到另外一個(gè)切片的數(shù)據(jù),此時(shí)我們可以新創(chuàng)建一個(gè)切片,使用內(nèi)置的copy
函數(shù)來復(fù)制原切片元素的值。示例代碼如下:
package main import "fmt" func main() { array := [5]int{1, 2, 3, 4, 5} slice := array[1:4] // 復(fù)制切片創(chuàng)建一個(gè)獨(dú)立的底層數(shù)組 newSlice := make([]int, len(slice)) copy(newSlice, slice) fmt.Println("Original array:", array) // 輸出原始數(shù)組 [1 2 3 4 5] fmt.Println("Original slice:", slice) // 輸出初始切片 [2 3 4] fmt.Println("New slice:", newSlice) // 輸出新創(chuàng)建的切片 [2 3 4] // 修改newSlice的第一個(gè)元素 newSlice[0] = 10 fmt.Println("Modified array:", array)// 輸出修改后的數(shù)組 [1 2 3 4 5] fmt.Println("Original slice:", slice)// 輸出初始切片 [2 3 4] fmt.Println("New slice:", newSlice)// 輸出修改后的切片 [10 3 4] }
通過創(chuàng)建了一個(gè)新的切片newSlice
,它擁有獨(dú)立的底層數(shù)組,同時(shí)使用copy
函數(shù)復(fù)制原切片的值,我們現(xiàn)在修改newSlice
不會影響原始數(shù)組或原始切片。
2.2 注意自動(dòng)擴(kuò)容可能帶來的性能問題
在Go語言中,切片的容量是指底層數(shù)組的大小,而長度是切片當(dāng)前包含的元素?cái)?shù)量。當(dāng)切片的長度超過容量時(shí),Go語言會自動(dòng)擴(kuò)容切片。擴(kuò)容操作涉及到重新分配底層數(shù)組,并將原有數(shù)據(jù)復(fù)制到新的數(shù)組中。下面先通過一個(gè)示例代碼,演示切片的自動(dòng)擴(kuò)容機(jī)制:
package main import "fmt" func main() { slice := make([]int, 3, 5) // 創(chuàng)建一個(gè)初始長度為3,容量為5的切片 fmt.Println("Initial slice:", slice) // 輸出初始切片 [0 0 0] fmt.Println("Length:", len(slice)) // 輸出切片長度 3 fmt.Println("Capacity:", cap(slice)) // 輸出切片容量 5 slice = append(slice, 1, 2, 3) // 添加3個(gè)元素到切片,長度超過容量 fmt.Println("After appending:", slice) // 輸出擴(kuò)容后的切片 [0 0 0 1 2 3] fmt.Println("Length:", len(slice)) // 輸出切片長度 6 fmt.Println("Capacity:", cap(slice)) // 輸出切片容量 10 }
在上述代碼中,我們使用make
函數(shù)創(chuàng)建了一個(gè)初始長度為3,容量為5的切片slice
。然后,我們通過append
函數(shù)添加了3個(gè)元素到切片,導(dǎo)致切片的長度超過了容量。此時(shí),Go語言會自動(dòng)擴(kuò)容切片,創(chuàng)建一個(gè)新的底層數(shù)組,并將原有數(shù)據(jù)復(fù)制到新的數(shù)組中。最終,切片的長度變?yōu)?,容量變?yōu)?0。
但是切片的自動(dòng)擴(kuò)容機(jī)制,其實(shí)是存在性能開銷的,需要?jiǎng)?chuàng)建一個(gè)新的數(shù)組,同時(shí)將數(shù)據(jù)全部拷貝到新數(shù)組中,切片再引用新的數(shù)組。下面先通過基準(zhǔn)測試,展示沒有設(shè)置初始容量和設(shè)置了初始容量兩種情況下的性能差距:
package main import ( "fmt" "testing" ) func BenchmarkSliceAppendNoCapacity(b *testing.B) { for i := 0; i < b.N; i++ { var slice []int for j := 0; j < 1000; j++ { slice = append(slice, j) } } } func BenchmarkSliceAppendWithCapacity(b *testing.B) { for i := 0; i < b.N; i++ { slice := make([]int, 0, 1000) for j := 0; j < 1000; j++ { slice = append(slice, j) } } }
在上述代碼中,我們定義了兩個(gè)基準(zhǔn)測試函數(shù):BenchmarkSliceAppendNoCapacity
和BenchmarkSliceAppendWithCapacity
。其中,BenchmarkSliceAppendNoCapacity
測試了在沒有設(shè)置初始容量的情況下,循環(huán)追加元素到切片的性能;BenchmarkSliceAppendWithCapacity
測試了在設(shè)置了初始容量的情況下,循環(huán)追加元素到切片的性能。基準(zhǔn)測試結(jié)果如下:
BenchmarkSliceAppendNoCapacity-4 280983 4153 ns/op 25208 B/op 12 allocs/op
BenchmarkSliceAppendWithCapacity-4 1621177 712.2 ns/op 0 B/op 0 allocs/op
其中ns/op
表示每次操作的平均執(zhí)行時(shí)間,即函數(shù)執(zhí)行的耗時(shí)。B/op
表示每次操作的平均內(nèi)存分配量,即每次操作分配的內(nèi)存大小。allocs/op
表示每次操作的平均內(nèi)存分配次數(shù)。
可以看到,在設(shè)置了初始容量的情況下,性能要明顯優(yōu)于沒有設(shè)置初始容量的情況。循環(huán)追加1000個(gè)元素到切片時(shí),設(shè)置了初始容量的情況下平均每次操作耗時(shí)約為712.2納秒,而沒有設(shè)置初始容量的情況下平均每次操作耗時(shí)約為4153 納秒。這是因?yàn)樵O(shè)置了初始容量避免了頻繁的擴(kuò)容操作,提高了性能。
所以,雖然切片的自動(dòng)擴(kuò)容好用,但是其也是存在代價(jià)的。更好得使用切片,應(yīng)該避免頻繁的擴(kuò)容操作,這里可以在創(chuàng)建切片時(shí)預(yù)估所需的容量,并提前指定切片的容量,這樣可以減少擴(kuò)容次數(shù),提高性能。需要注意的是,如果你不知道切片需要多大的容量,可以使用適當(dāng)?shù)某跏既萘?,然后根?jù)需要?jiǎng)討B(tài)擴(kuò)容。
2.3 注意切片參數(shù)修改原始數(shù)據(jù)的陷阱
在Go語言中,切片是引用類型。當(dāng)將切片作為參數(shù)傳遞給函數(shù)時(shí),實(shí)際上是傳遞了底層數(shù)組的引用。這意味著在函數(shù)內(nèi)部修改切片的元素會影響到原始切片。下面是一個(gè)示例代碼來說明這個(gè)問題:
package main import "fmt" func modifySlice(slice []int) { slice[0] = 10 fmt.Println("Modified slice inside function:", slice) } func main() { originalSlice := []int{1, 2, 3} fmt.Println("Original slice:", originalSlice) modifySlice(originalSlice) fmt.Println("Original slice after function call:", originalSlice) }
在上述代碼中,我們定義了一個(gè)modifySlice
函數(shù),它接收一個(gè)切片作為參數(shù),并在函數(shù)內(nèi)部修改了切片的第一個(gè)元素,并追加了一個(gè)新元素。然后,在main
函數(shù)中,我們創(chuàng)建了一個(gè)初始切片originalSlice
,并將其作為參數(shù)傳遞給modifySlice
函數(shù)。當(dāng)我們運(yùn)行代碼時(shí),輸出如下:
Original slice: [1 2 3]
Modified slice inside function: [10 2 3]
Original slice after function call: [10 2 3]
可以看到,在modifySlice
函數(shù)內(nèi)部,我們修改了切片的第一個(gè)元素并追加了一個(gè)新元素。這導(dǎo)致了函數(shù)內(nèi)部切片的變化。然而,當(dāng)函數(shù)返回后,原始切片originalSlice
數(shù)據(jù)也受到影響。
如果我們希望函數(shù)內(nèi)部的修改不影響原始切片,可以通過復(fù)制切片來解決。修改示例代碼如下:
package main import "fmt" func modifySlice(slice []int) { newSlice := make([]int, len(slice)) copy(newSlice, slice) newSlice[0] = 10 fmt.Println("Modified slice inside function:", newSlice) } func main() { originalSlice := []int{1, 2, 3} fmt.Println("Original slice:", originalSlice) modifySlice(originalSlice) fmt.Println("Original slice after function call:", originalSlice) }
通過使用make
函數(shù)創(chuàng)建一個(gè)新的切片newSlice
,并使用copy
函數(shù)將原始切片復(fù)制到新切片中,我們確保了函數(shù)內(nèi)部操作的是新切片的副本。這樣,在修改新切片時(shí)不會影響原始切片的值。當(dāng)我們運(yùn)行修改后的代碼時(shí),輸出如下:
Original slice: [1 2 3]
Modified slice inside function: [10 2 3]
Original slice after function call: [1 2 3]
可以看到,原始切片保持了不變,函數(shù)內(nèi)部的修改只影響了復(fù)制的切片。這樣我們可以避免在函數(shù)間傳遞切片時(shí)對原始切片造成意外修改。
3. 總結(jié)
本文深入探討了Go語言切片的一些注意事項(xiàng),旨在幫助讀者更好地使用切片。
首先,切片是對底層數(shù)組的引用。修改切片的元素會直接影響到底層數(shù)組以及其他引用該數(shù)組的切片。如果需要避免修改一個(gè)切片影響其他切片或底層數(shù)組,可以使用copy函數(shù)創(chuàng)建一個(gè)獨(dú)立的底層數(shù)組。
其次,切片的自動(dòng)擴(kuò)容可能帶來性能問題。當(dāng)切片的長度超過容量時(shí),Go語言會自動(dòng)擴(kuò)容切片,需要重新分配底層數(shù)組并復(fù)制數(shù)據(jù)。為了避免頻繁的擴(kuò)容操作,可以在創(chuàng)建切片時(shí)預(yù)估所需的容量,并提前指定切片的容量。
最后,需要注意切片作為參數(shù)傳遞給函數(shù)時(shí),函數(shù)內(nèi)部的修改會影響到原始切片。如果希望函數(shù)內(nèi)部的修改不影響原始切片,可以通過復(fù)制切片來解決。
了解和掌握這些切片的注意事項(xiàng)和技巧,可以避免意外的程序行為。
到此這篇關(guān)于Go語言中一定要知道的切片使用注意事項(xiàng)總結(jié)的文章就介紹到這了,更多相關(guān)Go語言切片內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談golang fasthttp踩坑經(jīng)驗(yàn)
本文主要介紹了golang fasthttp踩坑經(jīng)驗(yàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11Go語言如何利用Mutex保障數(shù)據(jù)讀寫正確
這篇文章主要介紹了互斥鎖的實(shí)現(xiàn)機(jī)制,以及?Go?標(biāo)準(zhǔn)庫的互斥鎖?Mutex?的基本使用方法,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考一下2023-05-05GO語言實(shí)現(xiàn)簡單的目錄復(fù)制功能
這篇文章主要介紹了GO語言實(shí)現(xiàn)簡單的目錄復(fù)制功能,通過新建及復(fù)制內(nèi)容等操作最終實(shí)現(xiàn)復(fù)制目錄的功能效果,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2014-12-12Golang實(shí)現(xiàn)延遲調(diào)用的項(xiàng)目實(shí)踐
本文主要介紹了Golang實(shí)現(xiàn)延遲調(diào)用的項(xiàng)目實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02GO語言不固定參數(shù)函數(shù)與匿名函數(shù)的使用
本文主要介紹了GO語言不固定參數(shù)函數(shù)與匿名函數(shù)的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03詳解go 動(dòng)態(tài)數(shù)組 二維動(dòng)態(tài)數(shù)組
這篇文章主要介紹了go 動(dòng)態(tài)數(shù)組 二維動(dòng)態(tài)數(shù)組,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07