淺談Golang?Slice切片如何擴容的實現(xiàn)
一、Slice數(shù)據結構是什么?
切片(slice)是 Golang 中一種比較特殊的數(shù)據結構,這種數(shù)據結構更便于使用和管理數(shù)據集合。切片是圍繞動態(tài)數(shù)組的概念構建的,可以按需自動增長和縮小。切片(slice)是可以看做是一個長度可變的數(shù)組。
切片(slice)自身并不是動態(tài)數(shù)組或者數(shù)組指針。它內部實現(xiàn)的數(shù)據結構通過指針引用底層數(shù)組,設定相關屬性將數(shù)據讀寫操作限定在指定的區(qū)域內。
切片(slice)是對數(shù)組一個連續(xù)片段的引用,所以切片是一個引用類型。
二、詳細代碼
1.數(shù)據結構
slice的結構體由3部分構成,Pointer 是指向一個數(shù)組的指針,len 代表當前切片的長度,cap 是當前切片的容量。cap 總是大于等于 len 的。
通常我們在對 slice 進行 append 等操作時,可能會造成slice的自動擴容。
代碼如下(示例):
type slice struct { array unsafe.Pointer len int cap int }
2.擴容原則
- 如果切片的容量小于1024個元素,那么擴容的時候slice的cap就乘以2;一旦元素個數(shù)超過1024個元素,增長因子就變成1.25,即每次增加原來容量的四分之一。
- 如果擴容之后,還沒有觸及原數(shù)組的容量,那么,切片中的指針指向的位置,就還是原數(shù)組,如果擴容之后,超過了原數(shù)組的容量,那么,Go就會開辟一塊新的內存,把原來的值拷貝過來,這種情況絲毫不會影響到原數(shù)組。
3.如何理解擴容規(guī)則一
規(guī)則一:
如果切片的容量小于1024個元素,那么擴容的時候slice的cap就乘以2;一旦元素個數(shù)超過1024個元素,增長因子就變成1.25,即每次增加原來容量的四分之一。
1.當小于1024個元素時
代碼如下(示例):
func main() { // 建立容量為 2 的 切片 addCap := make([]string, 0, 2) // 插入一個數(shù),占一個容量 addCap = append(addCap, "1") // 打印此時的地址 fmt.Println("addCap 1", addCap, cap(addCap), &addCap[0]) // 插入一個數(shù),占一個容量 // 再打印此時的地址 addCap = append(addCap, "1") fmt.Println("addCap 2", addCap, cap(addCap), &addCap[0]) // 插入一個數(shù),占一個容量 // 再打印此時的地址 addCap = append(addCap, "1") // 此時三個數(shù)已經超出容量,那么切片容量將擴容,此時地址也將變成新的地址 fmt.Println("addCap 3", addCap, cap(addCap), &addCap[0]) }
結果(示例):
2.當大于1024個元素時
代碼如下(示例):
func main() { // 建立容量為 1022 的 切片 addCap1024 := make([]int, 1022, 1024) // 插入一個數(shù),占一個容量 容量 1023 addCap1024 = append(addCap1024, 1) // 打印此時的地址 fmt.Println("addCap1024 1", cap(addCap1024), &addCap1024[0]) // 插入一個數(shù),占一個容量 // 再打印此時的地址 addCap1024 = append(addCap1024, 1) fmt.Println("addCap1024 2", cap(addCap1024), &addCap1024[0]) // 插入一個數(shù),占一個容量 // 再打印此時的地址 addCap1024 = append(addCap1024, 1) // 此時三個數(shù)已經超出容量1024,那么切片容量將擴容,此時地址也將變成新的地址 fmt.Println("addCap1024 3", cap(addCap1024), &addCap1024[0]) }
結果(示例):
此時容量Cap 增加了 1280 - 1024 = 256 ,也就是 1024 的 25 %
即 增長因子就變成1.25,即每次增加原來容量的四分之一。
4.如何理解擴容規(guī)則二
規(guī)則一:
如果擴容之后,還沒有觸及原數(shù)組的容量,那么,切片中的指針指向的位置,就還是原數(shù)組,如果擴容之后,超過了原數(shù)組的容量,那么,Go就會開辟一塊新的內存,把原來的值拷貝過來,這種情況絲毫不會影響到原數(shù)組。
1.簡單理解內存地址更換
代碼如下(示例):
func main() { // 建立容量為 2 的 切片 addCap := make([]string, 0, 2) // 插入一個數(shù),占一個容量 addCap = append(addCap, "1") // 打印此時的地址 fmt.Println("addCap 1", addCap, cap(addCap), &addCap[0]) // 插入一個數(shù),占一個容量 // 再打印此時的地址 addCap = append(addCap, "1") fmt.Println("addCap 2", addCap, cap(addCap), &addCap[0]) // 將 oth 的指針指向切片地址 // 再打印此時的地址 和 addCap 一樣,即未觸及容量時,還是原數(shù)組 oth := addCap[0:1] fmt.Println("oth 1",oth,cap(oth),&oth[0]) //此時修改原數(shù)組 oth 所指向的地址不變,但第一個數(shù)的值已經更改 3 addCap[0] = "3" fmt.Println("oth 2",oth,cap(oth),&oth[0]) // 插入一個數(shù),占一個容量 // 再打印此時的地址 addCap = append(addCap, "1") //此時再修改 已經是擴容后的新地址 原數(shù)組將保持不變 即oth 所指向的地址的值不變 addCap[0] = "4" // 此時三個數(shù)已經超出容量,那么切片容量將擴容,此時地址也將變成新的地址,第一個數(shù)也將修改為 4 fmt.Println("addCap 3", addCap, cap(addCap), &addCap[0]) // 但 oth 依然保留著原數(shù)組的指針地址,所以依然還是 3 fmt.Println("oth 3",oth,cap(oth),&oth[0]) }
結果(示例):
此時容量Oth 指向原切片位置,而擴容后的新的切片指向了新的位置,做的修改將無法影響原切片。
總結
通過以上兩個例子可以輕松了解在Golang中切片擴容的主要形式。而因為切片的底層也是是在連續(xù)的內存塊中分配的,所以切片還能獲得索引、迭代以及為垃圾回收優(yōu)化的好處,非常適合我們深入學習。
到此這篇關于淺談Golang Slice切片如何擴容的實現(xiàn)的文章就介紹到這了,更多相關Golang Slice切片擴容內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Go語言服務器開發(fā)實現(xiàn)最簡單HTTP的GET與POST接口
這篇文章主要介紹了Go語言服務器開發(fā)實現(xiàn)最簡單HTTP的GET與POST接口,實例分析了Go語言http包的使用技巧,需要的朋友可以參考下2015-02-02Go?語言?net/http?包使用之HTTP?服務器、客戶端與中間件詳解
Go 語言標準庫中的net/http包十分的優(yōu)秀,提供了非常完善的 HTTP 客戶端與服務端的實現(xiàn),僅通過幾行代碼就可以搭建一個非常簡單的 HTTP 服務器,本文給大家介紹Go語言net/http包使用之HTTP服務器、客戶端與中間件的操作,感興趣的朋友一起看看吧2025-05-05