Golang動態(tài)數(shù)組的實現(xiàn)示例
什么是動態(tài)數(shù)組
動態(tài)數(shù)組(Dynamic Array)是一種在需要時能夠自動改變其大小的數(shù)組。與靜態(tài)數(shù)組(Static Array)不同,靜態(tài)數(shù)組的大小在聲明時就已確定,且之后不能更改,而動態(tài)數(shù)組的大小則是根據(jù)需要動態(tài)變化的。動態(tài)數(shù)組通常通過某種數(shù)據(jù)結(jié)構(gòu)(如鏈表、數(shù)組加額外信息等)來實現(xiàn),以便在需要時能夠擴展或縮小其容量。(關(guān)鍵字:動態(tài)擴縮容,基于鏈表或數(shù)組實現(xiàn))
在大多數(shù)現(xiàn)代編程語言中,如C++的std::vector、Java的ArrayList、Python的列表(list)等,都提供了動態(tài)數(shù)組的功能。這些實現(xiàn)通常會在內(nèi)部使用一個靜態(tài)數(shù)組來存儲元素,并跟蹤當前數(shù)組的大小和已分配的容量。當向動態(tài)數(shù)組添加元素而當前容量不足時,它們會自動分配一個新的、更大的數(shù)組,并將舊數(shù)組的元素復(fù)制到新數(shù)組中,然后再添加新元素。這個過程對用戶是透明的,用戶無需關(guān)心底層的內(nèi)存管理細節(jié)。
Go語言中的動態(tài)數(shù)組——切片(slice)
Go語言(Golang)中的切片(slice)是一種引用類型,它提供了對數(shù)組的抽象。切片是對數(shù)組一個連續(xù)片段的引用,但它比數(shù)組更靈活、更強大。以下是Go語言切片的一些主要特點:
動態(tài)數(shù)組:切片的大小不是固定的,它們可以根據(jù)需要增長和縮小。當向切片中添加更多元素,且切片容量不足時,Go會自動分配更大的內(nèi)存空間并復(fù)制原有元素到新空間中,從而允許切片繼續(xù)增長。
引用類型:切片是引用類型,這意味著切片變量存儲的是對底層數(shù)組的引用(即內(nèi)存地址),而不是數(shù)組的拷貝。因此,通過不同的切片變量操作同一個底層數(shù)組的元素時,這些操作會相互影響。
長度和容量:切片有兩個重要的屬性:長度(length)和容量(capacity)。長度是切片中元素的數(shù)量;容量是從切片的第一個元素開始到底層數(shù)組末尾的元素數(shù)量。切片的長度可以改變,但容量在切片創(chuàng)建時由底層數(shù)組的大小決定,且一般只能通過重新切片或使切片指向一個新的數(shù)組來改變。
基于數(shù)組:切片是對數(shù)組的一個連續(xù)片段的引用,但切片的使用比數(shù)組更加靈活和方便。切片可以動態(tài)地增長和縮小,而數(shù)組的大小在定義時就確定了,不能改變。
零值:切片的零值是
nil,表示切片不引用任何數(shù)組。一個nil切片的長度和容量都是0,并且沒有底層數(shù)組。切片操作:Go支持對切片進行切片操作,即可以從一個已存在的切片中再“切”出一個新的切片。這種操作基于原切片的底層數(shù)組,但可以有不同的長度和容量。
內(nèi)存連續(xù):切片的底層數(shù)組在內(nèi)存中是連續(xù)的,這使得對切片中的元素進行迭代訪問時效率很高。
內(nèi)置函數(shù)支持:Go的標準庫提供了許多內(nèi)置函數(shù)來操作切片,如
append用于向切片追加元素,copy用于切片間的元素復(fù)制,以及len和cap用于獲取切片的長度和容量等。
切片數(shù)據(jù)結(jié)構(gòu)
type slice struct {
// 底層數(shù)組指針(指向一塊連續(xù)內(nèi)存空間的起點)
array unsafe.Pointer
// 切片長度
len int
// 切片容量
cap int
}
cap>=len
切片的創(chuàng)建和初始化
//通過字面量創(chuàng)建
/**
創(chuàng)建字符串切片,長度和容量都是3
*/
slice1 := []string{"aaa","bbb","ccc"}
fmt.Println(slice1) //[aaa bbb ccc]
/**
創(chuàng)建整形切片,長度和容量都是4
*/
slice2 := []int{10,20,30,40}
fmt.Println(slice2) //[10 20 30 40]
//當使用切片字面量創(chuàng)建切片時,還可以設(shè)置初始長度和容量。
/**
創(chuàng)建一個長度和容量都是100的切片 索引0 = 10,索引99 = 1
*/
slice3 := []int{0:10,99:1}
fmt.Println(slice3) //[10 0 0 ... 1]
//通過 make() 函數(shù)創(chuàng)建切片
/**
創(chuàng)建長度和容量都是5的切片
*/
slice4 := make([]int,5)
fmt.Println(slice4) //[0 0 0 0 0] 默認對應(yīng)類型的零值
/**
創(chuàng)建長度=3,容量=5的切片
*/
slice5 := make([]string,3,5)
fmt.Println(slice5) //[ ]
/**
不允許創(chuàng)建容量小于長度的切片
*/
slice6 := make([]int,5,3)
fmt.Println(slice6) //invalid argument: length and capacity swapped
切片初始化源碼
func makeslice(et *_type, len, cap int) unsafe.Pointer {
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
if overflow || mem > maxAlloc || len < 0 || len > cap {
mem, overflow := math.MulUintptr(et.size, uintptr(len))
if overflow || mem > maxAlloc || len < 0 {
panicmakeslicelen()
}
panicmakeslicecap()
}
return mallocgc(mem, et, true)
}- math.MulUintptr 方法計算出初始化切片需要的內(nèi)存和空間大小
- 空間超限,直接panic
- mallocgc方法,為切片進行空間分配
切片在方法之間的傳遞
func main() {
sl := []int{1,2,3,4,5}
fmt.Println(sl) //[1 2 3 4 5]
change(sl)
fmt.Println(sl) //[10 2 3 4 5]
}
func change(sl []int) {
sl[0] = 10
}每次在方法之間傳遞切片時,會將切片實例本身進行一次值拷貝(也可以叫淺拷貝),會將array指針,len長度,cap容量進行拷貝所以方法中和方法外的切邊array指向同一片內(nèi)存空間,因此在局部方法中執(zhí)行修改操作時,還會根據(jù)這個地址信息影響到原 slice 所屬的內(nèi)存空間,從而對內(nèi)容發(fā)生影響;
但是如果在函數(shù)中的切片發(fā)生了擴容,此時內(nèi)外就是兩個獨立的切片;
截取切片
在Go語言中,可以通過指定起始索引和結(jié)束索引來截取一個新的切片(包前不包后);
slice[start:end] //slice要截取的原始切片 //start截取切片的開始位置,不填默認為0 //end截取切片的結(jié)束位置,不填默認為切片長度
s := []int{1, 2, 3, 4, 5}
// 截取整個切片
subS1 := s[:]
fmt.Println(subS1) // 輸出: [1 2 3 4 5]
// 截取索引1到4(不包含4)的元素
subS2 := s[1:4]
fmt.Println(subS2) // 輸出: [2 3 4]
// 從索引0開始截取到索引4(不包含4)
subS3 := s[:4]
fmt.Println(subS3) // 輸出: [1 2 3 4]
// 從索引2開始截取到切片末尾
subS4 := s[2:]
fmt.Println(subS4) // 輸出: [3 4 5]截取出的切片和原始切片的關(guān)系
在對切片 slice 執(zhí)行截取操作時,本質(zhì)上是一次引用傳遞操作,因為不論如何截取,底層復(fù)用的都是同一塊內(nèi)存空間中的數(shù)據(jù),只不過,截取動作會創(chuàng)建出一個新的 slice header 實例;
s := []int{2,3,4,5}
s1 := s[1:]
往切片中新增元素(append)
通過append方法可以實現(xiàn)在切片的末尾添加元素
func main() {
s := []int{1, 2, 3}
// 使用 append 函數(shù)在切片末尾添加元素 4
s = append(s, 4)
fmt.Println(s) // 輸出: [1 2 3 4]
}
func main() {
s := []int{1, 2, 3}
// 使用 append 函數(shù)在切片末尾添加多個元素
s = append(s, 4, 5, 6)
fmt.Println(s) // 輸出: [1 2 3 4 5 6]
}
func main() {
s1 := []int{1, 2, 3}
s2 := []int{4, 5, 6}
// 使用 append 函數(shù)將 s2 添加到 s1 的末尾
s1 = append(s1, s2...) // 注意 s2 后的 ... 用于將 s2 展開為元素序列
fmt.Println(s1) // 輸出: [1 2 3 4 5 6]
}
func main() {
s := []int{1, 3, 4, 5}
// 在索引 1 的位置插入元素 2
// 首先,將索引 1 及其之后的元素向后移動一位
s = append(s[:1], append([]int{2}, s[1:]...)...)
fmt.Println(s) // 輸出: [1 2 3 4 5]
}刪除切片中的元素
在Go語言中,切片(slice)本身并不提供直接的刪除元素的方法,因為切片是對底層數(shù)組的抽象,而數(shù)組的大小是固定的。但是,通過以下幾種方式來實現(xiàn)“刪除”切片中的元素;
//使用切片操作刪除元素(適用于刪除末尾元素)
s := []int{1, 2, 3, 4, 5}
// 刪除切片末尾的元素
s = s[:len(s)-1]
fmt.Println(s) // 輸出: [1 2 3 4]
//使用append和切片操作刪除任意位置的元素
s := []int{1, 2, 3, 4, 5}
// 假設(shè)我們要刪除索引為2的元素(值為3)
index := 2
// 將index之后的元素(不包括index)復(fù)制到前面,然后截斷切片
s = append(s[:index], s[index+1:]...)
fmt.Println(s) // 輸出: [1 2 4 5]
//使用循環(huán)和條件語句刪除滿足條件的元素
s := []int{1, 2, 3, 4, 5}
// 刪除所有值為3的元素
var newS []int
for _, value := range s {
if value != 3 {
newS = append(newS, value)
}
}
s = newS
fmt.Println(s) // 輸出: [1 2 4 5]
拷貝切片
slice 的拷貝可以分為簡單拷貝和完整拷貝兩種類型;
要實現(xiàn)簡單拷貝(淺拷貝),我們只需要對切片的字面量進行賦值傳遞即可,這樣相當于創(chuàng)建出了一個新的 slice header 實例,但是其中的指針 array、容量 cap 和長度 len 仍和老的 slice header 實例相同;
slice 的完整復(fù)制(深拷貝),指的是會創(chuàng)建出一個和 slice 容量大小相等的獨立的內(nèi)存區(qū)域,并將原 slice 中的元素一一拷貝到新空間中,在實現(xiàn)上,slice 的完整復(fù)制可以調(diào)用系統(tǒng)方法 copy;
s := []int{1, 2, 3}
// 分配一個新的切片,長度和容量與s相同
sCopy := make([]int, len(s))
// 使用copy函數(shù)復(fù)制元素
copy(sCopy, s)
// 現(xiàn)在sCopy是s的一個獨立拷貝
fmt.Println(sCopy) // 輸出: [1 2 3]切片的擴容
什么時候會觸發(fā)擴容
當 slice 當前的長度 len 與容量 cap 相等時,下一次 append 操作就會引發(fā)一次切片擴容;
擴容步驟

1.計算目標容量
case1:如果新切片的長度>舊切片容量的兩倍,則新切片容量就為新切片的長度case2:
- 如果舊切片的容量小于256,那么新切片的容量就是舊切片的容量的兩倍
- 反之需要用舊切片容量按照1.25倍的增速,直到>=新切片長度,為了更平滑的過渡,每次擴大1.25倍,還會加上3/4* 256
2.進行內(nèi)存對齊
需要按照Go內(nèi)存管理的級別去對齊內(nèi)存,最終容量以這個為準
到此這篇關(guān)于Golang動態(tài)數(shù)組的實現(xiàn)示例的文章就介紹到這了,更多相關(guān)Golang動態(tài)數(shù)組內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言實現(xiàn)一個簡單的并發(fā)聊天室的項目實戰(zhàn)
本文主要介紹了Go語言實現(xiàn)一個簡單的并發(fā)聊天室的項目實戰(zhàn),文中根據(jù)實例編碼詳細介紹的十分詳盡,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03
一文帶你了解Golang中select的實現(xiàn)原理
select是go提供的一種跟并發(fā)相關(guān)的語法,非常有用。本文將介紹?Go?語言中的?select?的實現(xiàn)原理,包括?select?的結(jié)構(gòu)和常見問題、編譯期間的多種優(yōu)化以及運行時的執(zhí)行過程2023-02-02
golang 實現(xiàn)一個負載均衡案例(隨機,輪訓(xùn))
這篇文章主要介紹了golang 實現(xiàn)一個負載均衡案例(隨機、輪訓(xùn)),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04
goland遠程調(diào)試k8s上容器的實現(xiàn)
本文主要介紹了goland遠程調(diào)試k8s上容器的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
golang gin 監(jiān)聽rabbitmq隊列無限消費的案例代碼
這篇文章主要介紹了golang gin 監(jiān)聽rabbitmq隊列無限消費,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-12-12

