很多人對Go語言的array
和slice
傻傻分不清楚,今天我們就從底層出發,來聊聊它倆到底有什麼區別。
陣列
幾乎所有計算機語言,陣列的實現都是相似的:一段連續的記憶體,Go語言也一樣,Go語言的陣列底層實現就是一段連續的記憶體空間。每個元素有唯一一個索引(或者叫下標
)來訪問。如下圖所示,下圖是[5]int{1:10, 2:20}
陣列的內部實現邏輯圖:
由於記憶體連續,CPU很容易計算索引(即陣列的下標
),可以快速迭代陣列裡的所有元素。
Go語言的陣列不同於C語言或者其他語言的陣列,C語言的陣列變數是指向陣列第一個元素的指標;而Go語言的陣列是一個值,Go語言中的陣列是值型別,一個陣列變數就表示著整個陣列,意味著Go語言的陣列在傳遞的時候,傳遞的是原陣列的拷貝。你可以理解為Go語言的陣列是一種有序的struct
slice
切片是一個很小的物件,是對陣列進行了抽象,並提供相關的操作方法。切片有三個屬性欄位:長度、容量和指向陣列的指標。
上圖中,ptr
指的是指向array的pointer,len
是指切片的長度, cap
指的是切片的容量。現在,我想你對陣列和切片有了一個本質的認識。
切片有多種宣告方式,每種初始化方式對應的邏輯圖是怎樣的呢?
對於s := make([]byte, 5)
和s := []byte{...}
的方式
對於s = s[2:4]
的方式
對於nil
的切片即var s []byte
對應的邏輯圖是
在此有一個說明:nil
切片和空
切片是不太一樣的,空切片即s := make([]byte, 0)
或者s := []byte{}
出來的切片
空切片的邏輯圖為:
空切片指標不為nil,而nil切片指標為nil。但是,不管是空切片還是nil切片,對其呼叫內建函式append()
、len
和cap
的效果都是一樣的,感受不到任何區別。
擴容
slice這種資料結構便於使用和管理資料集合,可以理解為是一種“動態陣列”,slice
也是圍繞動態陣列的概念來構建的。既然是動態陣列,那麼slice是如何擴容的呢?
請記住以下兩條規則:
- 如果切片的容量小於1024個元素,那麼擴容的時候slice的cap就翻番,乘以2;一旦元素個數超過1024個元素,增長因子就變成1.25,即每次增加原來容量的四分之一。
- 如果擴容之後,還沒有觸及原陣列的容量,那麼,切片中的指標指向的位置,就還是原陣列,如果擴容之後,超過了原陣列的容量,那麼,Go就會開闢一塊新的記憶體,把原來的值拷貝過來,這種情況絲毫不會影響到原陣列。
知道了一下規則,請看下面程式,試問輸出結果:
import (
"fmt"
)
func main(){
array := [4]int{10, 20, 30, 40}
slice := array[0:2]
newSlice := append(append(append(slice, 50), 100), 150)
newSlice[1] += 1
fmt.Println(slice)
}
複製程式碼
輸出:
[10 20]
複製程式碼
答對了嗎?
參考文獻: 《Go in action》
https://blog.golang.org/go-slices-usage-and-internals