上次我們分享的字串相關的內容我們回顧一下
- 分享了字串具體是啥
- GO 中字串的特性,為什麼不能被修改
- 字串 GO 原始碼是如何構建的 ,原始碼檔案在
src/runtime/
下的string.go
- 字串 和
[]byte
的由來和應用場景 - 字串與
[]byte
相互轉換
要是對GO 對 字串 的編碼還有點興趣的話, 歡迎檢視文章 GO 中 string 的實現原理
slice 是什麼?
有沒有覺得很熟悉,上次分享的 string
型別 對應的資料結構 的前兩個引數 與 切片的資料結構的前兩個引數是一樣的
看看GO 的 src/runtime/
下的 slice.go
原始碼,我們可以找到 slice的資料結構
type slice struct {
array unsafe.Pointer
len int
cap int
}
// unsafe.Pointer 型別如下
// Pointer represents a pointer to an arbitrary type. There are four special operations
// available for type Pointer that are not available for other types:
// - A pointer value of any type can be converted to a Pointer.
// - A Pointer can be converted to a pointer value of any type.
// - A uintptr can be converted to a Pointer.
// - A Pointer can be converted to a uintptr.
// Pointer therefore allows a program to defeat the type system and read and write
// arbitrary memory. It should be used with extreme care.
type Pointer *ArbitraryType
切片GO 的一種資料型別 , 是對陣列的一個連續片段的引用
切片的底層結構是一個結構體,對應有三個引數
- array
是一個unsafe.Pointer
指標,指向一個具體的底層陣列
- len
指的是切片的長度
- cap
指的是切片的容量
有沒有覺得,切片和我們瞭解的陣列好像是一樣的,但是好像又不一樣
slice 和 陣列的區別是啥?
大概有如下幾個區別
- 陣列是複製傳遞的,而切片是引用傳遞的
在GO 裡面,傳遞陣列,是通過拷貝的方式
傳遞切片是通過引用的方式,這裡說的引用,指的是 切片資料結構中array
欄位,其餘欄位預設是值傳遞
- 陣列是相同型別的長度固定的序列
陣列是相同型別的,一組記憶體空間連續的資料,他的每一個元素的資料型別都是一樣的,且陣列的長度一開始就確定好了,且不能做變動
- 切片是一個結構,是一個資料物件,且物件裡面有 3 個引數
切片是引用型別,切片的長度是不固定的,可擴充套件的,GO 裡面操作切片真的是香
當然,切片也是離不開陣列的,因為他的array
指標就是指向的一個底層陣列,這個底層陣列,對使用者是不可見的
當使用切片的時候,陣列容量不夠的時候,這個底層陣列會自動重新分配,生成一個新的 切片(注意,這裡是生成一個新的切片)
如何建立 slice
建立一個新的切片有如下幾種方式:
- 使用make 方法建立 新的切片
- 使用陣列賦值的方式建立新的切片
使用make 方法建立 新的切片
新建一個 len 為 4,cap 為7 的切片:
func main(){
mySlice := make([]int,4,7)
// 此處的遍歷 長度是 len 的長度
for _,v :=range mySlice{
fmt.Printf("%v",v)
}
}
上述程式碼執行結果為
0000
為什麼不是 7 個 0,而是4 個
這裡要注意了:
此處遍歷遍歷切片的長度是 切片的 len
值, 而不是切片的容量 cap
值
使用陣列賦值的方式建立新的切片
- 建立一個 長度 為 8,資料型別為
int
的陣列 - 陣列的第5個元素和第6個元素複製給到新的切片
func main(){
arr := [8]int{}
mySlice := arr[4:6]
fmt.Println("len == ", len(mySlice))
fmt.Println("cap == ", cap(mySlice))
// 此處的遍歷 長度是 len 的長度
for _,v :=range mySlice{
fmt.Printf("%v",v)
}
}
上述程式碼執行結果為
len == 2
cap == 4
00
根據程式碼執行情況,列印出 00
,大家應該不會覺得奇怪
可是為什麼 cap 等於 4?
原因如下:
陣列的索引是從 0 開始的
上述程式碼 arr[4:6]
指的是將陣列的下標為 4 開始的位置,下標為 6 的為結束位置,這裡是不包含6自己的
根據 GO 中切片的原理,用陣列複製給到切片的時候,若複製的陣列元素後面還有內容的話,則後面的內容都作為切片的預留記憶體
即得到上述的結果,len == 2, cap == 4
不過這裡還是要注意,切片元素對應的地址,還是這個陣列元素對應的地址,使用的時候需要小心
slice 擴容原理是什麼?
我們就來模擬一下
- 新建一個 長度為 4 ,容量為 4 的切片
- 向切片中新增一個元素
- 列印最終切片的詳細情況
func main(){
mySlice := make([]int,4,4)
mySlice[0] = 3
mySlice[1] = 6
mySlice[2] = 7
mySlice[3] = 8
fmt.Printf("ptr == %p\n", &mySlice)
fmt.Println("len == ", len(mySlice))
fmt.Println("cap == ", cap(mySlice))
// 此處的遍歷 長度是 len 的長度
for _,v :=range mySlice{
fmt.Printf("%v ",v)
}
fmt.Println("")
mySlice = append(mySlice,5)
fmt.Printf("new_ptr == %p\n", &mySlice)
fmt.Println("new_len == ", len(mySlice))
fmt.Println("new_cap == ", cap(mySlice))
// 此處的遍歷 長度是 len 的長度
for _,v :=range mySlice{
fmt.Printf("%v ",v)
}
}
執行上述程式碼,有如下效果:
ptr == 0x12004110
len == 4
cap == 4
3 6 7 8
new_ptr == 0x12004110
new_len == 5
new_cap == 8
3 6 7 8 5
根據案例,相信大家或多或少心裡有點感覺了吧
向一個容量為 4 且長度為 4 的切片新增元素,我們發現切片的容量變成了 8
我們來看看切片擴容的規則是這樣的:
- 如果原來的切片容量小於1024
那麼新的切片容量就會擴充套件成原來的 2 倍
- 如果原切片容量大於等於1024
那麼新的切片容量就會擴充套件成為原來的1.25倍
我們再來梳理一下上述擴容原理的步驟是咋弄的
上述切片擴容,大致分為如下 2 種情況:
- 新增的元素,加入到切片中,若原切片容量夠
那麼就直接新增元素,且切片的len ++ ,此處的新增可不是直接賦值,可是使用 append函式的方式,例如
func main(){
mys := make([]int,3,5)
fmt.Println("len == ", len(mys))
fmt.Println("cap == ", cap(mys))
mys[0] = 1
mys[1] = 1
mys[2] = 1
// mys[3] = 2 會程式崩潰
mys = append(mys,2)
fmt.Println("len == ", len(mys))
fmt.Println("cap == ", cap(mys))
for _,v :=range mys{
fmt.Printf("%v",v)
}
}
- 若原切片容量不夠,則先將切片擴容,再將原切片資料追加到新的切片中
簡單說一下空切片和 nil 切片
平時我們在使用JSON 序列化的時候,明明切片為空
為什麼有的 JSON 輸出是[] , 有的 JSON 輸出是 null
我們來看看這個例子
func main(){
// 是一個空物件
var mys1 []int
// 是一個物件,物件裡面是一個切片,這個切片沒有元素
var mys2 = []int{}
json1, _ := json.Marshal(mys1)
json2, _ := json.Marshal(mys2)
fmt.Println(string(json1))
fmt.Println(string(json2))
}
執行結果為
null
[]
原因是這樣的:
- mys1 是一個空物件
- mys2 不是一個空物件,是一個正常的物件,但是物件裡面的為空
總結
- 分享了切片是什麼
- 切片和陣列的區別
- 切片的資料結構
- 切片的擴容原理
- 空切片 和 nil 切片的區別
歡迎點贊,關注,收藏
朋友們,你的支援和鼓勵,是我堅持分享,提高質量的動力
好了,本次就到這裡,下一次 GO 中 map 的實現原理分享
技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。
我是小魔童哪吒,歡迎點贊關注收藏,下次見~
本作品採用《CC 協議》,轉載必須註明作者和本文連結