1. 切片和數組的底層關系
Go語言切片的數據結構是一個結構體:
type slice struct {array unsafe.Pointerlen intcap int
}
Go語言中切片的內部結構包含地址、大小和容量。將數組比喻成一個蛋糕,那么切片就是需要切的那一塊,而那一塊的的大小就是切片的大小,而容量可以理解為裝這一塊蛋糕的袋子的大小。通過切片,我們可以快速地對數組進行操作。
從數組或切片中獲取新切片
從數組中獲取新切片,代碼如下:
var a = [3]int{1, 2, 3}
fmt.Println(a, a[1:2])
a是一個被初始化為長度為3,值為{1,2,3}
的一維數組。使用a[1:2]
可以生成一個新的切片:
[1 2 3] [2]
從數組中獲取原切片,代碼如下:
a := []int{1, 2, 3}
fmt.Println(a[:])
對a使用a[:]
操作后,生成的切片與原數組內容一致。
清空切片
a := []int{1, 2, 3}
fmt.Println(a[0:0])
對a使用a[0:0]
操作后,切片大小為0,相當于清空了切片。
綜上,我們發現獲取切片,實際上是對底層數組的某一片段拿出來進行操作。非常類似于C語言的指針,可以通過指針運算,來達到類似切片的目的,但是存在野指針的風險。
而切片在指針的基礎上增加了大小,使用中不允許對切片的內部地址和大小進行手動調整。因此比指針更加安全、更加強大。
簡單來說,切片在內部對指針進行了限制和管理,從而實現更加安全且快速地對數據集合進行操作。
2. 切片的擴容機制
使用make函數構造切片
若需要動態地構建一個切片,則需要使用make函數:
make( []Type, size, cap )
size
指這個切片的實際大小;cap
指的是預分配的內存空間大小。
make函數構造切片的過程中是一定進行了內存分配的操作
擴容
當對切片進行動態地添加元素時,若切片大小超出容量,容量會以2的倍數進行擴容。
我們看這樣一個案例:
silce := make([]int, 0)for i := 0; i < 10; i++ {silce = append(silce, i+1)fmt.Printf("len:%d cap:%d p:%p\n", len(silce), cap(silce), silce)}
可以發現:切片的大小和容量的關系只有在切片的大小超過切片的容量時,才會觸發切片容量的擴容,且每次擴容都是2倍擴容。
len:1 cap:1 p:0xc00000a0c8
len:2 cap:2 p:0xc00000a110
len:3 cap:4 p:0xc000012220
len:4 cap:4 p:0xc000012220
len:5 cap:8 p:0xc0000183c0
len:6 cap:8 p:0xc0000183c0
len:7 cap:8 p:0xc0000183c0
len:8 cap:8 p:0xc0000183c0
len:9 cap:16 p:0xc000100080
len:10 cap:16 p:0xc000100080
觀察每次擴容,切片的地址都會進行改變,這是為什么呢?
我們在上文講"切片與數組"的關系時,分析過:切片的本質是一種"安全的指針"。而底層數組的內存大小被分配結束后是無法進行擴容的(本質上是順序表)。因此,若要進行擴容,那么只能創建一個新的數組(Go內部規定容量為原先的2倍),然后將原數組的數據轉移到新數組內,并讓切片的指針重新指向新的數組。
因此,每次擴容都會進行一次“搬家”,而搬家后,家的指針自然要改變到新家。
未完待續