[toc]
- Posted by 微博@Yangsc_o
- 原創(chuàng)文章刹枉,版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
摘要
本文主要回顧一下Slice實(shí)現(xiàn)的使用和基本原理
Slice數(shù)據(jù)結(jié)構(gòu)
源碼包中 src/runtime/slice.go:slice 定義了Slice的數(shù)據(jù)結(jié)構(gòu): array指針指向底層數(shù)組,len表示切片長(zhǎng)度屈呕,cap表示底層數(shù)組容量微宝。
type slice struct {
array unsafe.Pointer
len int
cap int
}
使用make創(chuàng)建Slice
該Slice長(zhǎng)度為5,即可以使用下標(biāo)slice[0] ~ slice[4]來操作里面的元素虎眨,capacity為10蟋软,表示后續(xù)向 slice添加新的元素時(shí)可以不必重新分配內(nèi)存,直接使用預(yù)留內(nèi)存即可嗽桩。
使用數(shù)組創(chuàng)建Slice
使用數(shù)組來創(chuàng)建Slice時(shí)岳守,Slice將與原數(shù)組共用一部分內(nèi)存。 例如碌冶,語(yǔ)句 slice := array[5:7] 所創(chuàng)建的Slice湿痢,結(jié)構(gòu)如下圖所示:
切片從數(shù)組array[5]開始,到數(shù)組array[7]結(jié)束(不含array[7])扑庞,即切片長(zhǎng)度為2譬重,數(shù)組后面的內(nèi)容都作為切 片的預(yù)留內(nèi)存,即capacity為5罐氨。
- 數(shù)組和切片操作可能作用于同一塊內(nèi)存臀规,這也是使用過程中需要注意的地方。
Slice 擴(kuò)容
使用append向Slice追加元素時(shí)栅隐,如果Slice空間不足塔嬉,將會(huì)觸發(fā)Slice擴(kuò)容,擴(kuò)容實(shí)際上重新一配一塊更大的內(nèi) 存租悄,將原Slice數(shù)據(jù)拷貝進(jìn)新Slice谨究,然后返回新Slice,擴(kuò)容后再將數(shù)據(jù)追加進(jìn)去泣棋。
例如记盒,當(dāng)向一個(gè)capacity為5,且length也為5的Slice再次追加1個(gè)元素時(shí)外傅,就會(huì)發(fā)生擴(kuò)容纪吮,如下圖所示:
擴(kuò)容操作只關(guān)心容量俩檬,會(huì)把原Slice數(shù)據(jù)拷貝到新Slice,追加數(shù)據(jù)由append在擴(kuò)容結(jié)束后完成碾盟。上圖可見棚辽,擴(kuò)容后 新的Slice長(zhǎng)度仍然是5,但容量由5提升到了10冰肴,原Slice的數(shù)據(jù)也都拷貝到了新Slice指向的數(shù)組中屈藐。
擴(kuò)容容量的選擇遵循以下規(guī)則: 如果原Slice容量小于1024,則新Slice容量將擴(kuò)大為原來的2倍;
如果原Slice容量大于等于1024熙尉,則新Slice容量將擴(kuò)大為原來的1.25倍; 使用append()向Slice添加一個(gè)元素的實(shí)現(xiàn)步驟如下:
- 假如Slice容量夠用联逻,則將新元素追加進(jìn)去,Slice.len++检痰,返回原Slice
- 原Slice容量不夠包归,則將Slice先擴(kuò)容,擴(kuò)容后得到新Slice
- 將新元素追加進(jìn)新Slice铅歼,Slice.len++公壤,返回新的Slice。
Slice Copy
使用copy()內(nèi)置函數(shù)拷貝兩個(gè)切片時(shí)椎椰,會(huì)將源切片的數(shù)據(jù)逐個(gè)拷貝到目的切片指向的數(shù)組中厦幅,拷貝數(shù)量取兩個(gè)切片長(zhǎng)度的最小值。
例如長(zhǎng)度為10的切片拷貝到長(zhǎng)度為5的切片時(shí)慨飘,將會(huì)拷貝5個(gè)元素确憨。 也就是說,copy過程中不會(huì)發(fā)生擴(kuò)容瓤的。
特殊切片
跟據(jù)數(shù)組或切片生成新的切片一般使用 slice := array[start:end] 方式缚态,這種新生成的切片并沒有指定切片的容量, 實(shí)際上新切片的容量是從start開始直至array的結(jié)束堤瘤。
比如下面兩個(gè)切片玫芦,長(zhǎng)度和容量都是一致的,使用共同的內(nèi)存地址:
sliceA := make([]int, 5, 10)
sliceB := sliceA[0:5]
根據(jù)數(shù)組或切片生成切片還有另一種寫法本辐,即切片同時(shí)也指定容量桥帆,即slice[start:end:cap], 其中cap即為新 切片的容量,當(dāng)然容量不能超過原切片實(shí)際值慎皱,如下所示:
sliceA := make([]int, 5, 10) //length = 5; capacity = 10
sliceB := sliceA[0:5] //length = 5; capacity = 10
sliceC := sliceA[0:5:5] //length = 5; capacity = 5
這切片方法不常見老虫,在Golang源碼里能夠見到,不過非常利于切片的理解茫多。
總結(jié)
- 創(chuàng)建切片時(shí)可跟據(jù)實(shí)際需要預(yù)分配容量祈匙,盡量避免追加過程中擴(kuò)容操作,有利于提升性能;
- 切片拷貝時(shí)需要判斷實(shí)際拷貝的元素個(gè)數(shù)
- 謹(jǐn)慎使用多個(gè)切片操作同一個(gè)數(shù)組,以防讀寫沖突
- 每個(gè)切片都指向一個(gè)底層數(shù)組
- 每個(gè)切片都保存了當(dāng)前切片的長(zhǎng)度夺欲、底層數(shù)組可用容量
- 使用len()計(jì)算切片長(zhǎng)度時(shí)間復(fù)雜度為O(1)跪帝,不需要遍歷切片
- 使用cap()計(jì)算切片容量時(shí)間復(fù)雜度為O(1),不需要遍歷切片
- 通過函數(shù)傳遞切片時(shí)些阅,不會(huì)拷貝整個(gè)切片伞剑,因?yàn)榍衅旧碇皇莻€(gè)結(jié)構(gòu)體而矣
- 使用append()向切片追加元素時(shí)有可能觸發(fā)擴(kuò)容,擴(kuò)容后將會(huì)生成新的切片
參考
《go專家編程》