Go 高階指南 slice 實現原理

slice 切片,因爲其可以方便的進行擴容、傳遞等,在實際應用中比數組更加靈活。切片的一些基礎使用可以看下之前的文章,傳送門

這裏我們再更加深層次的瞭解下 slice 。先看幾段代碼,測試下諸位對 slice 的理解程度。

答題環節

  1. 以下程序輸出什麼?
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] 地址相同。

  1. 以下程序輸出什麼?
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

slice 實現原理

源碼包 src/runtime/slice.go 定義的 slice 數據結構爲:

type slice struct {
 array unsafe.Pointer
 len   int
 cap   int
}

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 copy

使用 copy() 內置函數拷貝兩個切片時,會將源切片的數據逐個拷貝到目的的切片指向的數組中,拷貝數量取**「兩個切片長度的最小值」**。

例如將長度爲 10 的切片拷貝到長度爲 5 的切片時,將會拷貝 5 個元素。也就是說 「copy 不會發生擴容」

  1. 根據數組或切片來生成新的切片一般使用 slice := array[start:end] 方式,這種新生成的切片沒有指定容量,新切片的容量是從 start 開始到 array 的結束(注意並不是到 end)。

  2. 另一種寫法,生成新切片同時指定其容量:slice[start:end:cap] ,其中的 cap 爲新切片的容量,容量不能超過原切片實際值。

總結

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/_Y6QMzHHty22yfD7TNDpTA