日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

淺談Golang?Slice切片如何擴容的實現_Golang

作者:頭禿貓輕王 ? 更新時間: 2022-04-25 編程語言

一、Slice數據結構是什么?

切片(slice)是 Golang 中一種比較特殊的數據結構,這種數據結構更便于使用和管理數據集合。切片是圍繞動態數組的概念構建的,可以按需自動增長和縮小。切片(slice)是可以看做是一個長度可變的數組。
切片(slice)自身并不是動態數組或者數組指針。它內部實現的數據結構通過指針引用底層數組,設定相關屬性將數據讀寫操作限定在指定的區域內。
切片(slice)是對數組一個連續片段的引用,所以切片是一個引用類型。

二、詳細代碼

1.數據結構

slice的結構體由3部分構成,Pointer 是指向一個數組的指針,len 代表當前切片的長度,cap 是當前切片的容量。cap 總是大于等于 len 的。
通常我們在對 slice 進行 append 等操作時,可能會造成slice的自動擴容。

代碼如下(示例):

type slice struct {
	array unsafe.Pointer
	len int
	cap int
}

2.擴容原則

  • 如果切片的容量小于1024個元素,那么擴容的時候slice的cap就乘以2;一旦元素個數超過1024個元素,增長因子就變成1.25,即每次增加原來容量的四分之一。
  • 如果擴容之后,還沒有觸及原數組的容量,那么,切片中的指針指向的位置,就還是原數組,如果擴容之后,超過了原數組的容量,那么,Go就會開辟一塊新的內存,把原來的值拷貝過來,這種情況絲毫不會影響到原數組。

3.如何理解擴容規則一

規則一:

如果切片的容量小于1024個元素,那么擴容的時候slice的cap就乘以2;一旦元素個數超過1024個元素,增長因子就變成1.25,即每次增加原來容量的四分之一。

1.當小于1024個元素時

代碼如下(示例):

func main() {
	// 建立容量為 2 的 切片
	addCap := make([]string, 0, 2)
	// 插入一個數,占一個容量
	addCap = append(addCap, "1")
	// 打印此時的地址
	fmt.Println("addCap 1", addCap, cap(addCap), &addCap[0])
	// 插入一個數,占一個容量
	// 再打印此時的地址
	addCap = append(addCap, "1")
	fmt.Println("addCap 2", addCap, cap(addCap), &addCap[0])
	// 插入一個數,占一個容量
	// 再打印此時的地址
	addCap = append(addCap, "1")
	// 此時三個數已經超出容量,那么切片容量將擴容,此時地址也將變成新的地址
	fmt.Println("addCap 3", addCap, cap(addCap), &addCap[0])

}

結果(示例):

結果

2.當大于1024個元素時

代碼如下(示例):

func main() {
	// 建立容量為 1022 的 切片
	addCap1024 := make([]int, 1022, 1024)
	// 插入一個數,占一個容量 容量 1023
	addCap1024 = append(addCap1024, 1)
	// 打印此時的地址
	fmt.Println("addCap1024 1", cap(addCap1024), &addCap1024[0])
	// 插入一個數,占一個容量
	// 再打印此時的地址
	addCap1024 = append(addCap1024, 1)
	fmt.Println("addCap1024 2", cap(addCap1024), &addCap1024[0])
	// 插入一個數,占一個容量
	// 再打印此時的地址
	addCap1024 = append(addCap1024, 1)
	// 此時三個數已經超出容量1024,那么切片容量將擴容,此時地址也將變成新的地址
	fmt.Println("addCap1024 3", cap(addCap1024), &addCap1024[0])

}

結果(示例):

結果

此時容量Cap 增加了 1280 - 1024 = 256 ,也就是 1024 的 25 %
即 增長因子就變成1.25,即每次增加原來容量的四分之一。

4.如何理解擴容規則二

規則一:

如果擴容之后,還沒有觸及原數組的容量,那么,切片中的指針指向的位置,就還是原數組,如果擴容之后,超過了原數組的容量,那么,Go就會開辟一塊新的內存,把原來的值拷貝過來,這種情況絲毫不會影響到原數組。

1.簡單理解內存地址更換

代碼如下(示例):

func main() {
	// 建立容量為 2 的 切片
	addCap := make([]string, 0, 2)
	// 插入一個數,占一個容量
	addCap = append(addCap, "1")
	// 打印此時的地址
	fmt.Println("addCap 1", addCap, cap(addCap), &addCap[0])
	// 插入一個數,占一個容量
	// 再打印此時的地址
	addCap = append(addCap, "1")
	fmt.Println("addCap 2", addCap, cap(addCap), &addCap[0])
	// 將 oth 的指針指向切片地址
	// 再打印此時的地址 和 addCap 一樣,即未觸及容量時,還是原數組
	oth := addCap[0:1]
	fmt.Println("oth 1",oth,cap(oth),&oth[0])

	//此時修改原數組 oth 所指向的地址不變,但第一個數的值已經更改 3
	addCap[0] = "3"
	fmt.Println("oth 2",oth,cap(oth),&oth[0])

	// 插入一個數,占一個容量
	// 再打印此時的地址
	addCap = append(addCap, "1")
	//此時再修改 已經是擴容后的新地址 原數組將保持不變 即oth 所指向的地址的值不變
	addCap[0] = "4"
	// 此時三個數已經超出容量,那么切片容量將擴容,此時地址也將變成新的地址,第一個數也將修改為 4
	fmt.Println("addCap 3", addCap, cap(addCap), &addCap[0])
	// 但 oth 依然保留著原數組的指針地址,所以依然還是 3
	fmt.Println("oth 3",oth,cap(oth),&oth[0])

}

結果(示例):

結果

此時容量Oth 指向原切片位置,而擴容后的新的切片指向了新的位置,做的修改將無法影響原切片。

總結

通過以上兩個例子可以輕松了解在Golang中切片擴容的主要形式。而因為切片的底層也是是在連續的內存塊中分配的,所以切片還能獲得索引、迭代以及為垃圾回收優化的好處,非常適合我們深入學習。

原文鏈接:https://blog.csdn.net/moer0/article/details/122933748

欄目分類
最近更新