很多人對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