Go 高階指南 slice 實現原理
slice 切片,因爲其可以方便的進行擴容、傳遞等,在實際應用中比數組更加靈活。切片的一些基礎使用可以看下之前的文章,傳送門。
這裏我們再更加深層次的瞭解下 slice 。先看幾段代碼,測試下諸位對 slice 的理解程度。
答題環節
- 以下程序輸出什麼?
package main
import (
"fmt"
)
func main() {
var array [20]int
var slice = array[10:11]
fmt.Println("lenth: ", len(slice))
fmt.Println("capacity: ", cap(slice))
fmt.Println(&slice[0] == &array[10])
}
答
案
是:
lenth: 1
capacity: 10
true
slice 根據數組 array 創建,與數組共享存儲空間,slice 起始位置是 array[5],長度爲 1,容量爲 5, slice[0] 和 array[5] 地址相同。
- 以下程序輸出什麼?
package main
import (
"fmt"
)
func main() {
orderLen := 5
order := make([]uint16, 2 * orderLen)
pollorder := order[:orderLen:orderLen]
lockorder := order[orderLen:][:orderLen:orderLen]
fmt.Println("len(pollorder) = ", len(pollorder))
fmt.Println("cap(pollorder) = ", cap(pollorder))
fmt.Println("len(lockorder) = ", len(lockorder))
fmt.Println("cap(lockorder) = ", cap(lockorder))
}
答
案
是:
len(pollorder) = 5
cap(pollorder) = 5
len(lockorder) = 5
cap(lockorder) = 5
-
order
[low:high:max]
意思是對 order 進行切片,新切片範圍是[low, high)
, 新切片容量是 max; -
order 長度爲 2 倍的 orderLen;
-
pollorder 切片是 order 的前半部分切片;
-
lockorder 是 order 的後半部分切片,即原 order 分成了兩段;
-
所以,pollorder 和 lockerorder 的長度和容量都是 orderLen,即 5。
slice 實現原理
源碼包 src/runtime/slice.go 定義的 slice 數據結構爲:
type slice struct {
array unsafe.Pointer
len int
cap int
}
-
array 指針指向底層數組
-
len 表示切片長度
-
cap 表示切片容量
make 創建 slice
make 創建 slice 可以同時指定其長度和容量,底層會分配一個數組,數組的長度即容量。
slice = make([]int,5,10): 表示改 slice 長度爲 5,容量爲 10。
數組創建 slice
使用數組來創建 slice 時,slice 與原數組共用一部分內存。
slice := array[5:7] 所創建的 slice ,結構如下圖:
切片從數組 array[5] 開始,到數組 array[7] 結束(不包含 array[7]),切片長度爲 2,數組後面的內容作爲切片的預留內存,即容量爲 5。
slice 擴容
在使用 append 向 slice 追加元素時,若 slice 空間不足則會發生擴容,擴容會重新分配一塊更大的內存,將原 slice 拷貝到新 slice ,然後返回新 slice。擴容後再將數據追加進去。
擴容操作只只對容量,擴容後的 slice 長度不變,容量變化規則如下:
-
若 slice 容量夠用,則將新元素追加進去,slice.len++,返回原 slice
-
若 slice 容量不夠用,將 slice 先擴容,擴容得到新 slice,將新元素追加進新 slice,slice.len++,返回新 slice。
slice copy
使用 copy() 內置函數拷貝兩個切片時,會將源切片的數據逐個拷貝到目的的切片指向的數組中,拷貝數量取**「兩個切片長度的最小值」**。
例如將長度爲 10 的切片拷貝到長度爲 5 的切片時,將會拷貝 5 個元素。也就是說 「copy 不會發生擴容」。
-
根據數組或切片來生成新的切片一般使用 slice := array[start:end] 方式,這種新生成的切片沒有指定容量,新切片的容量是從 start 開始到 array 的結束(注意並不是到 end)。
-
另一種寫法,生成新切片同時指定其容量:slice[start:end:cap] ,其中的 cap 爲新切片的容量,容量不能超過原切片實際值。
總結
-
創建切片時可根據實際需要預分配容量,儘量避免追加過程中擴容操作,有利於提升性能
-
切片拷貝時需要判斷實際拷貝的元素個數
-
謹慎使用多個切片操作同一個數組,以防讀寫衝突
-
每個切片都指向一個底層數組
-
每個切片都保存了當前切片的長度、底層數組可用容量
-
使用 len()、cap() 計算切片長度、容量時,時間複雜度均爲 O(1),不需要遍歷切片
-
通過函數傳遞切片時,不會拷貝整個切片,因爲切片本身只是個結構體而矣
-
使用 append() 向切片追加元素時有可能觸發擴容,擴容後將會生成新的切片
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/_Y6QMzHHty22yfD7TNDpTA