淺談Golang?Slice切片如何擴(kuò)容的實(shí)現(xiàn)
一、Slice數(shù)據(jù)結(jié)構(gòu)是什么?
切片(slice)是 Golang 中一種比較特殊的數(shù)據(jù)結(jié)構(gòu),這種數(shù)據(jù)結(jié)構(gòu)更便于使用和管理數(shù)據(jù)集合。切片是圍繞動(dòng)態(tài)數(shù)組的概念構(gòu)建的,可以按需自動(dòng)增長(zhǎng)和縮小。切片(slice)是可以看做是一個(gè)長(zhǎng)度可變的數(shù)組。
切片(slice)自身并不是動(dòng)態(tài)數(shù)組或者數(shù)組指針。它內(nèi)部實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)通過(guò)指針引用底層數(shù)組,設(shè)定相關(guān)屬性將數(shù)據(jù)讀寫(xiě)操作限定在指定的區(qū)域內(nèi)。
切片(slice)是對(duì)數(shù)組一個(gè)連續(xù)片段的引用,所以切片是一個(gè)引用類(lèi)型。
二、詳細(xì)代碼
1.數(shù)據(jù)結(jié)構(gòu)
slice的結(jié)構(gòu)體由3部分構(gòu)成,Pointer 是指向一個(gè)數(shù)組的指針,len 代表當(dāng)前切片的長(zhǎng)度,cap 是當(dāng)前切片的容量。cap 總是大于等于 len 的。
通常我們?cè)趯?duì) slice 進(jìn)行 append 等操作時(shí),可能會(huì)造成slice的自動(dòng)擴(kuò)容。
代碼如下(示例):
type slice struct { array unsafe.Pointer len int cap int }
2.擴(kuò)容原則
- 如果切片的容量小于1024個(gè)元素,那么擴(kuò)容的時(shí)候slice的cap就乘以2;一旦元素個(gè)數(shù)超過(guò)1024個(gè)元素,增長(zhǎng)因子就變成1.25,即每次增加原來(lái)容量的四分之一。
- 如果擴(kuò)容之后,還沒(méi)有觸及原數(shù)組的容量,那么,切片中的指針指向的位置,就還是原數(shù)組,如果擴(kuò)容之后,超過(guò)了原數(shù)組的容量,那么,Go就會(huì)開(kāi)辟一塊新的內(nèi)存,把原來(lái)的值拷貝過(guò)來(lái),這種情況絲毫不會(huì)影響到原數(shù)組。
3.如何理解擴(kuò)容規(guī)則一
規(guī)則一:
如果切片的容量小于1024個(gè)元素,那么擴(kuò)容的時(shí)候slice的cap就乘以2;一旦元素個(gè)數(shù)超過(guò)1024個(gè)元素,增長(zhǎng)因子就變成1.25,即每次增加原來(lái)容量的四分之一。
1.當(dāng)小于1024個(gè)元素時(shí)
代碼如下(示例):
func main() { // 建立容量為 2 的 切片 addCap := make([]string, 0, 2) // 插入一個(gè)數(shù),占一個(gè)容量 addCap = append(addCap, "1") // 打印此時(shí)的地址 fmt.Println("addCap 1", addCap, cap(addCap), &addCap[0]) // 插入一個(gè)數(shù),占一個(gè)容量 // 再打印此時(shí)的地址 addCap = append(addCap, "1") fmt.Println("addCap 2", addCap, cap(addCap), &addCap[0]) // 插入一個(gè)數(shù),占一個(gè)容量 // 再打印此時(shí)的地址 addCap = append(addCap, "1") // 此時(shí)三個(gè)數(shù)已經(jīng)超出容量,那么切片容量將擴(kuò)容,此時(shí)地址也將變成新的地址 fmt.Println("addCap 3", addCap, cap(addCap), &addCap[0]) }
結(jié)果(示例):
2.當(dāng)大于1024個(gè)元素時(shí)
代碼如下(示例):
func main() { // 建立容量為 1022 的 切片 addCap1024 := make([]int, 1022, 1024) // 插入一個(gè)數(shù),占一個(gè)容量 容量 1023 addCap1024 = append(addCap1024, 1) // 打印此時(shí)的地址 fmt.Println("addCap1024 1", cap(addCap1024), &addCap1024[0]) // 插入一個(gè)數(shù),占一個(gè)容量 // 再打印此時(shí)的地址 addCap1024 = append(addCap1024, 1) fmt.Println("addCap1024 2", cap(addCap1024), &addCap1024[0]) // 插入一個(gè)數(shù),占一個(gè)容量 // 再打印此時(shí)的地址 addCap1024 = append(addCap1024, 1) // 此時(shí)三個(gè)數(shù)已經(jīng)超出容量1024,那么切片容量將擴(kuò)容,此時(shí)地址也將變成新的地址 fmt.Println("addCap1024 3", cap(addCap1024), &addCap1024[0]) }
結(jié)果(示例):
此時(shí)容量Cap 增加了 1280 - 1024 = 256 ,也就是 1024 的 25 %
即 增長(zhǎng)因子就變成1.25,即每次增加原來(lái)容量的四分之一。
4.如何理解擴(kuò)容規(guī)則二
規(guī)則一:
如果擴(kuò)容之后,還沒(méi)有觸及原數(shù)組的容量,那么,切片中的指針指向的位置,就還是原數(shù)組,如果擴(kuò)容之后,超過(guò)了原數(shù)組的容量,那么,Go就會(huì)開(kāi)辟一塊新的內(nèi)存,把原來(lái)的值拷貝過(guò)來(lái),這種情況絲毫不會(huì)影響到原數(shù)組。
1.簡(jiǎn)單理解內(nèi)存地址更換
代碼如下(示例):
func main() { // 建立容量為 2 的 切片 addCap := make([]string, 0, 2) // 插入一個(gè)數(shù),占一個(gè)容量 addCap = append(addCap, "1") // 打印此時(shí)的地址 fmt.Println("addCap 1", addCap, cap(addCap), &addCap[0]) // 插入一個(gè)數(shù),占一個(gè)容量 // 再打印此時(shí)的地址 addCap = append(addCap, "1") fmt.Println("addCap 2", addCap, cap(addCap), &addCap[0]) // 將 oth 的指針指向切片地址 // 再打印此時(shí)的地址 和 addCap 一樣,即未觸及容量時(shí),還是原數(shù)組 oth := addCap[0:1] fmt.Println("oth 1",oth,cap(oth),&oth[0]) //此時(shí)修改原數(shù)組 oth 所指向的地址不變,但第一個(gè)數(shù)的值已經(jīng)更改 3 addCap[0] = "3" fmt.Println("oth 2",oth,cap(oth),&oth[0]) // 插入一個(gè)數(shù),占一個(gè)容量 // 再打印此時(shí)的地址 addCap = append(addCap, "1") //此時(shí)再修改 已經(jīng)是擴(kuò)容后的新地址 原數(shù)組將保持不變 即oth 所指向的地址的值不變 addCap[0] = "4" // 此時(shí)三個(gè)數(shù)已經(jīng)超出容量,那么切片容量將擴(kuò)容,此時(shí)地址也將變成新的地址,第一個(gè)數(shù)也將修改為 4 fmt.Println("addCap 3", addCap, cap(addCap), &addCap[0]) // 但 oth 依然保留著原數(shù)組的指針地址,所以依然還是 3 fmt.Println("oth 3",oth,cap(oth),&oth[0]) }
結(jié)果(示例):
此時(shí)容量Oth 指向原切片位置,而擴(kuò)容后的新的切片指向了新的位置,做的修改將無(wú)法影響原切片。
總結(jié)
通過(guò)以上兩個(gè)例子可以輕松了解在Golang中切片擴(kuò)容的主要形式。而因?yàn)榍衅牡讓右彩鞘窃谶B續(xù)的內(nèi)存塊中分配的,所以切片還能獲得索引、迭代以及為垃圾回收優(yōu)化的好處,非常適合我們深入學(xué)習(xí)。
到此這篇關(guān)于淺談Golang Slice切片如何擴(kuò)容的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang Slice切片擴(kuò)容內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言服務(wù)器開(kāi)發(fā)實(shí)現(xiàn)最簡(jiǎn)單HTTP的GET與POST接口
這篇文章主要介紹了Go語(yǔ)言服務(wù)器開(kāi)發(fā)實(shí)現(xiàn)最簡(jiǎn)單HTTP的GET與POST接口,實(shí)例分析了Go語(yǔ)言http包的使用技巧,需要的朋友可以參考下2015-02-02Go?語(yǔ)言?net/http?包使用之HTTP?服務(wù)器、客戶端與中間件詳解
Go 語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的net/http包十分的優(yōu)秀,提供了非常完善的 HTTP 客戶端與服務(wù)端的實(shí)現(xiàn),僅通過(guò)幾行代碼就可以搭建一個(gè)非常簡(jiǎn)單的 HTTP 服務(wù)器,本文給大家介紹Go語(yǔ)言net/http包使用之HTTP服務(wù)器、客戶端與中間件的操作,感興趣的朋友一起看看吧2025-05-05使用Go Validator有效驗(yàn)證數(shù)據(jù)示例分析
作為一名開(kāi)發(fā)者,確保Go應(yīng)用中處理的數(shù)據(jù)是有效和準(zhǔn)確的非常重要,Go Validator是一個(gè)開(kāi)源的數(shù)據(jù)驗(yàn)證庫(kù),為Go結(jié)構(gòu)體提供強(qiáng)大且易于使用的數(shù)據(jù)驗(yàn)證功能,本篇文章將介紹Go Validator庫(kù)的主要特點(diǎn)以及如何在Go應(yīng)用中使用它來(lái)有效驗(yàn)證數(shù)據(jù)2023-12-12詳解Go語(yǔ)言中獲取文件路徑的不同方法與應(yīng)用場(chǎng)景
在使用?Go?開(kāi)發(fā)項(xiàng)目時(shí),估計(jì)有不少人遇到過(guò)無(wú)法正確處理文件路徑的問(wèn)題,本文將嘗試從簡(jiǎn)單到復(fù)雜,詳細(xì)介紹?Go?中獲取路徑的不同方法及應(yīng)用場(chǎng)景,希望對(duì)大家有所幫助2024-02-02一文搞懂Go語(yǔ)言中defer關(guān)鍵字的使用
defer是golang中用的比較多的一個(gè)關(guān)鍵字,也是go面試題里經(jīng)常出現(xiàn)的問(wèn)題。今天就來(lái)整理一下關(guān)于defer的學(xué)習(xí)使用,希望對(duì)需要的朋友有所幫助2022-09-09利用go語(yǔ)言實(shí)現(xiàn)查找二叉樹(shù)中的最大寬度
這篇文章主要介紹了利用go語(yǔ)言實(shí)現(xiàn)查找二叉樹(shù)中的最大寬度,文章圍繞主題展開(kāi)詳細(xì)介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-05-05