今天開(kāi)工第一天凤薛,2023 xdm 什么目標(biāo),評(píng)論區(qū)聊聊晋柱。今天我們來(lái)聊聊切片(Slice)砸讳。
1. 說(shuō)在前面
在之前的 Go 語(yǔ)言基礎(chǔ)篇數(shù)組我們說(shuō)到數(shù)組的長(zhǎng)度是固定的琢融,申明了之后就不能改變其長(zhǎng)度。所以數(shù)組就會(huì)有很多的局限性簿寂。舉個(gè)例子:
Go 數(shù)組 s 我們此時(shí)已經(jīng)申明長(zhǎng)度為 5漾抬,我們不能在往數(shù)組 s 中添加新的元素了,在業(yè)務(wù)開(kāi)發(fā)中這樣的集合就不太適用陶耍。
2. 什么是切片
Go 語(yǔ)言中提供了一種靈活奋蔚、功能強(qiáng)悍的內(nèi)置類型切片(又稱動(dòng)態(tài)數(shù)組)Slice,和數(shù)組相比切片的長(zhǎng)度是不固定的,可以向其追加元素泊碑。
2.1 切片數(shù)據(jù)結(jié)構(gòu)
切片是一個(gè)引用類型坤按,底層源碼是用結(jié)構(gòu)體來(lái)表示的,其中:
- Data 是指向數(shù)組的指針
- Len 是當(dāng)前切片的長(zhǎng)度
- Cap 是當(dāng)前切片的容量馒过,也是 Data 數(shù)組的大小
Data
是一片連續(xù)的內(nèi)存空間臭脓,在這片空間中存儲(chǔ)著切片的所有元素,底層儲(chǔ)存上也是連續(xù)的腹忽。我們可以簡(jiǎn)單理解成:切片是一片連續(xù)的內(nèi)存空間加上長(zhǎng)度與容量的標(biāo)識(shí)来累。
從上圖可以發(fā)現(xiàn)切片其實(shí)就是數(shù)組的引用,我們可以在運(yùn)行期間修改其長(zhǎng)度與范圍窘奏,當(dāng)切片引用的數(shù)組長(zhǎng)度不足會(huì)觸發(fā)擴(kuò)容嘹锁,此時(shí)切片指向的數(shù)組會(huì)發(fā)生變化,但是對(duì)用戶來(lái)說(shuō)是無(wú)感知的着裹。
3. 切片
3.1 初始化
我們可以聲明一個(gè)未指定大小的數(shù)組來(lái)定義切片:
var identifier []type
其中:identifier
表示變量名领猾,type
表示切片中的元素類型,舉個(gè)例子
slice[0:10]
slice := []int{1, 2, 3, 4, 5, 6}
slice := make([]int, 10)
總結(jié)一下骇扇,大致有 3 種
- 通過(guò)下標(biāo)的方式獲得數(shù)組或者切片的一部分摔竿;
- 使用字面量初始化新的切片;
- 使用關(guān)鍵字 make 創(chuàng)建切片少孝;
3.2 切片的長(zhǎng)度和容量
切片是可索引的继低,Go 語(yǔ)言中切片提供內(nèi)置的方法。
- len() 方法獲取長(zhǎng)度稍走;
- cap() 可以計(jì)算切片的容量袁翁;
以上會(huì)輸出以下結(jié)果為:
len=3 cap=5 slice=[0 0 0]
3.3 切片空值判斷
一個(gè)切片在未初始化之前默認(rèn)為 nil,默認(rèn)長(zhǎng)度為 0婿脸,所以判斷是否使用 len(s) == 0
梦裂,不應(yīng)該使用 s == nil
。
3.4 切片遍歷
切片的遍歷方式和數(shù)組一樣盖淡,均支持索引遍歷和 for range 遍歷。
3.5 切片追加和擴(kuò)容
3.5.1 追加
日常開(kāi)發(fā)中凿歼,我們經(jīng)常會(huì)需要向一個(gè)切片中動(dòng)態(tài)追加元素褪迟。Go 語(yǔ)言內(nèi)置函數(shù) append()
可以向切片中追加元素,append
關(guān)鍵字支持一次添加一個(gè)或多個(gè)元素答憔,也支持追加一個(gè)切片的所有元素味赃。舉個(gè)例子:
其中,slice
無(wú)需初始化虐拓,在 append
中可以直接使用心俗。
注意:append
操作會(huì)發(fā)生擴(kuò)容。
3.5.2 擴(kuò)容
Go 語(yǔ)言中切片底層指向的是一個(gè)數(shù)組的指針,當(dāng)這個(gè)數(shù)組的容量可以放的下當(dāng)前新增的元素城榛,則向其追加元素揪利,當(dāng)?shù)讓拥臄?shù)組容量不足以容下新增的元素,此時(shí)切片會(huì)按照一定的策略進(jìn)行 “擴(kuò)容”狠持,此時(shí)切片指向就會(huì)指向新的數(shù)組指針疟位。舉個(gè)例子:
package main
import "fmt"
func main() {
var slice []int
for i := 0; i < 6; i++ {
slice = append(slice, i)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", slice, len(slice), cap(slice), slice)
}
}
以上會(huì)輸入以下結(jié)果:
[0] len:1 cap:1 ptr:0xc000016060
[0 1] len:2 cap:2 ptr:0xc000016090
[0 1 2] len:3 cap:4 ptr:0xc000014060
[0 1 2 3] len:4 cap:4 ptr:0xc000014060
[0 1 2 3 4] len:5 cap:8 ptr:0xc000074040
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc000074040
從上面的結(jié)果可以看出:
- append 函數(shù)會(huì)將元素追加到切片的尾部。
- 切片 slice 的容量是按照1喘垂,2甜刻,4,8正勒,16 進(jìn)行自動(dòng)擴(kuò)容得院,每次擴(kuò)容后都是擴(kuò)容前的2倍。
擴(kuò)容策略如下:(感興趣的同學(xué)可以看下底層源碼)
- 如果期望容量大于當(dāng)前容量的 2 倍就會(huì)使用期望容量章贞;
- 如果當(dāng)前切片的長(zhǎng)度小于 1024 就會(huì)將容量翻倍祥绞;
- 如果當(dāng)前切片的長(zhǎng)度大于 1024 就會(huì)每次增加 25% 的容量,直到新容量大于期望容量阱驾;
可以通過(guò)查看 $GOROOT/src/runtime/slice.go 源碼或 github 倉(cāng)庫(kù)(點(diǎn)擊跳轉(zhuǎn))就谜,其中擴(kuò)容核心代碼如下:
需要注意的是,切片擴(kuò)容還會(huì)根據(jù)切片中元素的類型不同而做不同的處理里覆,比如 int
和 string
類型的處理方式就不一樣丧荐。
3.6 拷貝切片
切片的拷貝雖然不是常見(jiàn)的操作,但是卻是我們學(xué)習(xí)切片實(shí)現(xiàn)原理必須要涉及的喧枷。
Go 語(yǔ)言內(nèi)置 copy() 函數(shù)可以迅速地將一個(gè)切片的數(shù)據(jù)復(fù)制到另外一個(gè)切片空間中虹统,copy() 函數(shù)的使用格式如下:
copy(destSlice, srcSlice []T)
其中:
- srcSlice: 數(shù)據(jù)來(lái)源切片
- destSlice: 目標(biāo)切片
需要注意的是,整塊拷貝內(nèi)存仍然會(huì)占用非常多的資源隧甚,在大切片上執(zhí)行拷貝操作時(shí)一定要注意對(duì)性能的影響车荔。
4 總結(jié)
本文講了一下切片的日常使用以及需要注意的點(diǎn),切片還有很多比較深一點(diǎn)的知識(shí)點(diǎn)戚扳,后面有機(jī)會(huì)再出一篇吧忧便,需要注意的是在遇到大切片擴(kuò)容或者復(fù)制時(shí)可能會(huì)發(fā)生大規(guī)模的內(nèi)存拷貝,一定要減少類似操作避免影響程序的性能帽借。
歡迎點(diǎn)贊關(guān)注珠增,公眾號(hào)搜:程序員祝融