Go語言中slice類型的使用小結(jié)
1. 數(shù)組和 slice
1.1. 數(shù)組
數(shù)組是具有固定長度且擁有零個(gè)或者多個(gè)相同數(shù)據(jù)類型元素的序列。由于數(shù)組的長度固定,所以在 Go 中很少直接使用。
var arr1 [3]int // 定義一個(gè)長度為 3,元素類型為 int 的數(shù)組,元素的值為其類型的零值
arr2 := [3]int{1, 2, 3} // 定義一個(gè)長度為 3,元素類型為 int 的數(shù)組,并用給定的值初始化每個(gè)元素
arr3 := [...]int{4, 5, 6, 7} // 如果使用 ... 代替數(shù)組的長度,Go 會根據(jù)初始化時(shí)數(shù)組元素的數(shù)量確定數(shù)據(jù)的長度
arr4 := [5]int{1: 10, 3: 30} // 支持索引為 1、3 的元素的值,其他索引的值為其類型的零值
fmt.Println(len(arr1), arr1)
fmt.Println(len(arr2), arr2)
fmt.Println(len(arr3), arr3)
fmt.Println(len(arr4), arr4)
輸出:
3 [0 0 0]
3 [1 2 3]
4 [4 5 6 7]
5 [0 10 0 30 0]
1.2. slice
在 Go 中,slice 表示一個(gè)擁有相同類型元素的可變長度的序列,寫作 []T,其中元素的類型都是 T。
slice 的定義位于 src/runtime/slice.go:
type slice struct {
array unsafe.Pointer
len int
cap int
}
其中:
array:指向 slice 底層數(shù)組第一個(gè)元素的指針;len:slice 中當(dāng)前存儲的元素個(gè)數(shù),可以用內(nèi)置函數(shù)len()獲??;cap:slice 底層數(shù)組的長度,可以用內(nèi)置函數(shù)cap()獲取。
1.3. 數(shù)組和 slice 的區(qū)別
- 數(shù)組長度是固定的,使用前必須確定長度。slice 長度可變。
- 數(shù)組是基于連續(xù)的內(nèi)存空間存儲數(shù)據(jù)的,作為函數(shù)參數(shù)傳遞時(shí)傳遞的是數(shù)組的拷貝。slice 是一個(gè)包含指向底層數(shù)組的指針、長度和容量的結(jié)構(gòu)體。
- 當(dāng)兩個(gè)數(shù)組長度相同,且數(shù)組元素類型可比較時(shí),可以使用
==和!=來比較這兩個(gè)數(shù)組;slice 只能和nil比較。
2. 創(chuàng)建 slice
s1 := make([]int, 5) // 創(chuàng)建長度為 5,容量為 5(默認(rèn)容量等于長度)的 slice
s2 := make([]int, 3, 10) // 創(chuàng)建長度為 3,顯式指定容量為 10 的 slice
s3 := []int{1, 2, 3} // 創(chuàng)建長度為 3,容量為 3 的 slice
s4 := []int{99: 1} // 創(chuàng)建長度為 100(長度由最大索引決定),容量為 100 的 slice。索引為 99 的元素顯式初始化為 1,其他元素為類型的零值(0)
fmt.Println(len(s1), cap(s1))
fmt.Println(len(s2), cap(s2))
fmt.Println(len(s3), cap(s3))
fmt.Println(len(s4), cap(s4))
3. 向 slice 追加元素
3.1.append函數(shù)
內(nèi)置函數(shù) append 用于將元素追加到 slice 的后面,返回追加后的 slice。
s1 = append(s1, 0) // 向 slice s1 追加元素 s2 = append(s2, 1, 2, 3) // 向 slice s2 追加多個(gè)元素 s3 = append(s3, s2...) // 向 slice s3 追加另一個(gè) slice s2
執(zhí)行 append 函數(shù)后,可能出現(xiàn)以下兩種情況:
- 當(dāng) slice 還有剩余容量時(shí),
append直接追加,底層數(shù)組不變; - 當(dāng)容量不足時(shí),
append觸發(fā)growslice,新分配一個(gè)更大的數(shù)組,并將原元素復(fù)制過去,原數(shù)組若無引用則被 GC 回收。
3.2. slice 的擴(kuò)容策略
Slice 的擴(kuò)容策略位于 src/runtime/slice.go:
// growslice 為 slice 分配新的底層存儲
// 參數(shù):
// oldPtr = 指向 slice 底層數(shù)組的指針
// newLen = 擴(kuò)容后的長度(oldLen + num)
// oldCap = 原始 slice 的容量
// num = 擴(kuò)容的元素?cái)?shù)量
// et = 元素類型
//
// 要求 newLen 大于 oldCap(即發(fā)生了 cap 變化)
func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice {
...
}
// nextslicecap 計(jì)算擴(kuò)容后的 slice 容量
//
// 參數(shù):
// newLen = 擴(kuò)容后的長度
// oldCap = 原始 slice 的容量
func nextslicecap(newLen, oldCap int) int {
...
}
在 Go 1.18 及之后:
- 如果 slice 擴(kuò)容后的元素個(gè)數(shù)大于兩倍擴(kuò)容前的容量,新容量等于擴(kuò)容后的元素個(gè)數(shù);
- 如果擴(kuò)容前的容量小于 256,新容量等于兩倍擴(kuò)容前的容量;
- 如果擴(kuò)容前的容量大于等于 256,新容量初始值等于擴(kuò)容前的容量,每次增加 25% + 192,直到值大于新長度。
在 Go 1.18 之前
- 如果 slice 擴(kuò)容后元素個(gè)數(shù)大于兩倍擴(kuò)容前的容量,新容量等于擴(kuò)容后元素個(gè)數(shù);
- 如果擴(kuò)容前的容量小于 1024,新容量等于兩倍擴(kuò)容前的容量;
- 如果擴(kuò)容前的容量大于等于 1024,新容量初始值等于擴(kuò)容前的容量,每次增加 25%,直到值大于新長度。
如何理解這次變動呢?比較下兩個(gè)方式的擴(kuò)容因子:
| 原始容量 | Go 1.18 之前擴(kuò)容因子 | Go 1.18 及之后擴(kuò)容因子 |
|---|---|---|
| < 256 | 2.0 | 2.0 |
| 256 | 2.0 | 2.0 |
| 512 | 2.0 | 1.625 |
| 1024 | 1.25 | 1.4375 |
| 2048 | 1.25 | 1.34375 |
| 4096 | 1.25 | 1.296875 |
| 超大容量 | 1.25 | 無限趨近 1.25 |
可以看到,舊方式中,舊容量從 1024 開始擴(kuò)容因子突降至 1.25;新方式中,舊容量從 256 開始,擴(kuò)容因子平滑過渡到 1.25。
3.3. 當(dāng) slice 作為函數(shù)參數(shù)
當(dāng) slice 作為函數(shù)參數(shù)傳遞時(shí),如果需要函數(shù)內(nèi)修改 slice 的長度并對外部生效,通常應(yīng)返回新的 slice,而不是傳遞 slice 的指針 *[]int(雖然也可行,但不符合習(xí)慣)。
當(dāng)我們將 slice 作為函數(shù)參數(shù)傳遞時(shí),實(shí)質(zhì)上傳遞的是一個(gè)指向底層數(shù)組的指針、元素的個(gè)數(shù)(len)和底層數(shù)組的長度(cap)。函數(shù)內(nèi)部對 len 和 cap 的修改在函數(shù)外部不會生效,而函數(shù)內(nèi)部對 slice 元素的修改則需要分情況討論。
如果在函數(shù)內(nèi)部只修改了 slice 元素的值,在函數(shù)外部這些修改也會生效;如果在函數(shù)內(nèi)部對 slice 進(jìn)行了元素追加(修改了 len),則分為以下兩種情況:
- 追加元素后 slice 的 cap 沒有改變。此時(shí)函數(shù)外部 slice 和函數(shù)內(nèi)部 slice 指向的底層數(shù)組一致,所以函數(shù)內(nèi)部的修改在函數(shù)外部也是生效的。但是函數(shù)外部 slice 的 len 沒有改變,因此看不到使用 append 函數(shù)追加的元素內(nèi)容。但函數(shù)內(nèi)部對追加元素前長度范圍內(nèi)元素的修改會影響到函數(shù)外。
- 追加元素后 slice 的 cap 發(fā)生了改變。此時(shí) append 操作會重新創(chuàng)建一個(gè)新的底層數(shù)組,這種情況下函數(shù)內(nèi)外的 slice 指向的底層數(shù)組不一樣,對函數(shù)內(nèi) slice 的修改完全不影響函數(shù)外的 slice。
如果函數(shù)內(nèi)部需要改變原 slice 的長度或容量,并需要在函數(shù)外部生效,可以選擇返回一個(gè)新的 slice 讓函數(shù)外部接收。
4. 拷貝 slice
直接將一個(gè) slice 賦值給另一個(gè) slice,屬于淺拷貝,兩個(gè) slice 還是使用同一個(gè)底層數(shù)組。如果需要深拷貝,可以使用 copy 函數(shù)或 append 函數(shù)。
使用 copy 函數(shù):
s1 := []int{1, 2, 3}
s2 := make([]int, len(s1)) // 需要提前分配長度。如果 s2 的長度不足,則只拷貝 len(s2) 個(gè)元素
n := copy(s2, s1) // 返回拷貝的元素?cái)?shù)量,也就是 len(src) 和 len(dst) 的最小值
使用 append 函數(shù):
s1 := []int{1, 2, 3}
s2 := append([]int(nil), s1...)
copy 效率較高,適合已有目標(biāo)切片或需要精確控制要拷貝的元素?cái)?shù)量時(shí)使用。append 寫法簡潔,適合一步完成創(chuàng)建和拷貝(但可能因擴(kuò)容機(jī)制而分配比所需更大的容量)。
到此這篇關(guān)于Go語言中slice類型的使用小結(jié)的文章就介紹到這了,更多相關(guān)Go語言 slice類型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang jwt鑒權(quán)的實(shí)現(xiàn)流程
本文主要介紹了golang jwt鑒權(quán)的實(shí)現(xiàn)流程,包含生成JWT令牌、客戶端存儲和發(fā)送JWT令牌、服務(wù)端驗(yàn)證JWT令牌等,具有一定的參考價(jià)值,感興趣的可以了解一下2025-02-02
go動態(tài)限制并發(fā)數(shù)量的實(shí)現(xiàn)示例
本文主要介紹了Go并發(fā)控制方法,通過帶緩沖通道和第三方庫實(shí)現(xiàn)并發(fā)數(shù)量限制,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-07-07
Golang設(shè)計(jì)模式工廠模式實(shí)戰(zhàn)寫法示例詳解
這篇文章主要為大家介紹了Golang 工廠模式實(shí)戰(zhàn)寫法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08

