go語言中的slice


slice有三個部分,第一部分,元素存哪里;第二部分,存多少個元素;第三部分,可以存多少個元素。
舉個栗子:
聲明一個整型slice
var ints []int
變量ints就有這樣三個部分組成
0
slice的元素要存在一個連續的內存中,實際上就是個數組,data就是這個底層數組的起始地址,但是目前只分配了這個切片結構,還沒有分配底層數組,所以data=nil,存儲元素個數為0,容量也為0。
如果通過make的方式定義這個變量:
var ints []int = make([]int,2,5)
不僅會分配這三部分結構,還會開辟一段內存作為他的底層數組,這里,make會為ints開辟一段容納5個整型元素的內存,還會把他們初始化為整型的默認值0
0
但是目前這個slice變量只存儲了兩個元素,所以data指向底層數組,len為2,cap為5
 
0
添加一個元素試試
ints=append(ints,1)
已經存儲了兩個,所以他是第三個,len修改為3
 
0
已經存儲的元素是可以安全讀寫的,但是超出這個范圍就是越界訪問,會發生panic
ints[0] = 1
ints[3] = 2 //panic
 
0
再來個例子,這一次我們看看字符串類型的slice,但是不用make,來試試new,new一個slice變量
ps:=new([]string)
同樣會分配這三部分結構,但他不負責底層數組的分配,所以data=nil,len=0,cap=0,new的返回值就是sllice結構的起始地址,所以ps他就是個地址,此時這個slice變量還沒有底層數組
 
0
所以這樣的操作是不允許的
(*ps)[0]="eggo"
那誰來給他分配底層數組呢?
append,通過append的方式添加元素,他就會給slice開辟底層數組
*ps=append(*ps,"eggo")
這里需要容納一個字符串元素的數組,而字符串類型有兩部分組成,一個是內容起始地址,指向字符串內容,還有一個是字節長度
0
再來看看和slice密切相關的底層數組
來看個例子,變量arr是容量為10的整型數組
arr:=[10]int{0,1,2,3,4,5,6,7,8,9}
注意,數組容量聲明了,就不能變了,我們可以吧不同的slice關聯到同一個數組,就像這樣,他們會共用底層數組,看看s1和s2的具體結構就明白了
s1:=arr[1:4]
s2:=arr[7:]
 
0
s1的元素是arr索引1-4左閉右開,所以123算是添加到s1中了,但是容量算是從data這里開始到底層數組結束,共9個元素,同樣的s2的元素從索引7開始直到結束,共3個元素,容量也是3,slice訪問和修改的都是底層數組的元素, s1[3]就是訪問越界了,可以通過修改s1:=arr[1:5]或者append添加元素來擴大可讀寫的區間范圍。
此時如果再給s2添加元素會怎樣?
s2=append(s2,10)
這個底層數組是不能用了,得開辟新數組,原來的元素要拷過來,還要添加新元素,元素個數改為4,容量為6
0
為什么容量為6呢,我們只添加了一個元素,為什么容量會從3變為6呢?這就要看sllice的擴容規則了
擴容規則第一步,預估擴容后的容量newCap,怎么預估,來看個例子
ints:=[]int{1,2}
ints=append(ints,3,4,5)
這里擴容前容量oldCap為2,添加三個元素,那至少得擴容到5吧,難道就是2+3=5?當然不是,預估也是有規則的:
如果擴容前容量翻倍還是小於所需最小容量,那么預估容量就等於所需最小容量
oldCap * 2 = 4 < 5
否則就要再細分,如果擴容前元素小於1024,則直接翻倍
oldLen < 1024---->newCap = oldCap *2
如果oldLen大於等於1024,那就先擴容個四分之一,也就是擴容到原來的1.25倍
所以這里ints添三個元素,擴容前容量oldCap*2 = 4 < 5 所以預估容量就是5
擴容規則第二步,預估容量只是預估的元素個數,這么多元素需要占多少內存呢?這就和元素類型掛鈎了,用預估容量*元素類型大小得到的就是所需的內存大小,難道直接分配所需的內存就OK了嗎?並不是,go語言中申請內存操作並不是直接和操作系統交互,而是和語言自身實現的內存管理模塊,他會提前向操作系統申請一批內存,分成常用的規格管理起來,我們申請內存時,他會幫我們匹配到足夠大,且最接近的內存規格。
這就是擴容規則第三步,將預估申請的內存,匹配到合適的內存規格,在之前的例子中,預估容量為5,在64位下int占8字節,就需要申請到5*8 = 40 bytes來存放擴容后的底層數組,而實際申請時,會匹配到48字節的內存規格(具體參考 go語言內存管理,內存規格是16bytes疊加的,8,16,32,48,64,80,96...),在這個例子中,每個元素占8字節,所以最終擴容后的容量為48 / 8 = 6。
趁熱打鐵,再來個例子:
a是string類型的slice,64位下每個元素占16字節
a := []string{"my","name","is"}
a:=append(a,"aggo")
第一步
oldCap * 2 > 4 //原容量翻倍大於所需最小容量
oldLen < 1024 //擴容前元素大小小於1024
newCap = oldCap * 2 = 6 //所以預估容量為原容量直接翻倍
第二步
newCap * 16 = 96 bytes //計算預估容量所需內存大小
第三步
匹配內存規格96字節,故擴容后容量為6
 
注:本文總結自B站UP主 幼麟實驗室的視頻,只為方便復習使用。
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM