Go slice實現原理剖析

2021-08-30 13:47:54 字數 2866 閱讀 1256

slice又稱動態陣列,依託陣列實現,可以方便的進行擴容、傳遞等,實際使用中比陣列更靈活。

正因為靈活,如果不了解其內部實現機制,有可能遭遇莫名的異常現象。slice的實現原理很簡單,本節試圖根據真實的使用場景,在原始碼中總結實現原理。

按照慣例,我們開始前先看幾段**用於檢測對slice的理解程度。

下面程式輸出什麼?

package main

import (

"fmt"

)func main()

程式解釋:

main函式中定義了乙個10個長度的整型陣列array,然後定義了乙個切片slice,切取陣列的第6個元素,最後列印slice的長度和容量,判斷切片的第乙個元素和陣列的第6個元素位址是否相等。

下面程式輸出什麼?

package main

import (

"fmt"

)func addelement(slice int, e int) int

func main()

下面程式由golang原始碼改編而來,程式輸出什麼?

package main

import (

"fmt"

)func main()

程式解釋:

該段程式源自select的實現**,程式中定義乙個長度為10的切片order,pollorder和lockorder分別是對order切片做了order[low:high:max]操作生成的切片,最後程式分別列印pollorder和lockorder的容量和長度。

參***:

order[low:high:max]操作意思是對order進行切片,新切片範圍是[low, high),新切片容量是max。order長度為2倍的orderlen,pollorder切片指的是order的前半部分切片,lockorder指的是order的後半部分切片,即原order分成了兩段。所以,pollorder和lockerorder的長度和容量都是orderlen,即5。

slice依託陣列實現,底層陣列對使用者遮蔽,在底層陣列容量不足時可以實現自動重分配並生成新的slice。 接下來按照實際使用場景分別介紹其實現機制。

原始碼包中src/runtime/slice.go:slice定義了slice的資料結構:

type slice struct
從資料結構看slice很清晰, array指標指向底層陣列,len表示切片長度,cap表示底層陣列容量。

使用make來建立slice時,可以同時指定長度和容量,建立時底層會分配乙個陣列,陣列的長度即容量。

例如,語句slice := make(int, 5, 10)所建立的slice,結構如下圖所示:

該slice長度為5,即可以使用下標slice[0] ~ slice[4]來操作裡面的元素,capacity為10,表示後續向slice新增新的元素時可以不必重新分配記憶體,直接使用預留記憶體即可。

使用陣列來建立slice時,slice將與原陣列共用一部分記憶體。

例如,語句slice := array[5:7]所建立的slice,結構如下圖所示:

切片從陣列array[5]開始,到陣列array[7]結束(不含array[7]),即切片長度為2,陣列後面的內容都作為切片的預留記憶體,即capacity為5。

陣列和切片操作可能作用於同一塊記憶體,這也是使用過程中需要注意的地方。

例如,當向乙個capacity為5,且length也為5的slice再次追加1個元素時,就會發生擴容,如下圖所示:

擴容容量的選擇遵循以下規則:

假如slice容量夠用,則將新元素追加進去,slice.len++,返回原slice

原slice容量不夠,則將slice先擴容,擴容後得到新slice

將新元素追加進新slice,slice.len++,返回新的slice。

使用copy()內建函式拷貝兩個切片時,會將源切片的資料逐個拷貝到目的切片指向的陣列中,拷貝數量取兩個切片長度的最小值。

例如長度為10的切片拷貝到長度為5的切片時,將會拷貝5個元素。

也就是說,copy過程中不會發生擴容。

跟據陣列或切片生成新的切片一般使用slice := array[start:end]方式,這種新生成的切片並沒有指定切片的容量,實際上新切片的容量是從start開始直至array的結束。

slicea := make(int, 5, 10)

sliceb := slicea[0:5]

根據陣列或切片生成切片還有另一種寫法,即切片同時也指定容量,即slice[start:end:cap], 其中cap即為新切片的容量,當然容量不能超過原切片實際值,如下所示:

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原始碼裡能夠見到,不過非常利於切片的理解。

Go defer實現原理剖析

defer語句用於延遲函式的呼叫,每次defer都會把乙個函式壓入棧中,函式返回前再把延遲的函式取出並執行。為了方便描述,我們把建立defer的函式稱為主函式,defer語句後面的函式稱為延遲函式。延遲函式可能有輸入引數,這些引數可能 於定義defer的函式,延遲函式也可能引用主函式用於返回的變數,...

附近的人實現原理詳細剖析

要實現附近的人這個功能,我們要經歷以下幾個環節 使用者定時上傳自己的定位資訊,並存到服務端的資料庫中 使用者發起查詢請求,服務端根據使用者提供的定位資訊去資料庫中查詢與他的經度緯度海拔最接近的其他使用者的定位資訊。服務端通過兩個定位資訊就能算出距離,按照遠近排好序後,連同對應的使用者賬戶,暱稱,頭像...

GO 互斥鎖實現原理剖析

互斥鎖是併發程式中對共享資源進行訪問控制的主要手段,對此go語言提供了非常簡單易用的mutex,mutex為一結構體型別,對外暴露兩個方法lock 和unlock 分別用於加鎖和解鎖。mutex使用起來非常方便,但其內部實現卻複雜得多,這包括mutex的幾種狀態。另外,我們也想 一下mutex重複解...