揭祕 Go 切片(Slice)的祕密
當向切片添加新參數時,底層數組會發生什麼變化?它會擴展以容納更多元素嗎?
在這篇文章中,我們將深入探討切片的內部工作原理,以及如何利用這些知識來進行更好的內存管理和性能優化。
具體而言,我們將探索 Go 中切片的底層實現和內存管理機制。
讓我們開始吧!
查看數組
要深入瞭解切片的結構,必須仔細查看其底層類型:數組。
func main() {
a := [5]int{}
fmt.Printf("%p, %p\n", &a, &a[0])
}
// 0x14000018240, 0x14000018240
正如您可能已經瞭解的那樣,數組中第一個元素的內存位置也是數組本身的內存位置(這意味着當您將數組傳遞給函數或賦值給變量時,您實際上是傳遞或賦值了第一個元素的內存地址)。
因此,數組的內存佈局是連續的內存塊,每個元素依次放置在相鄰的位置上。
切片的結構
切片有三個主要組成部分:
-
• 底層數組指針:底層數組指針指向切片第一個元素的內存位置。
-
• 長度:當前在切片中被使用或可訪問的元素數量。
-
• 容量:底層數組中可以存儲的總元素數量,從底層數組指針開始計算。
這些關於切片結構的信息是從 Go 運行時庫中獲取的。現在,讓我們更詳細地瞭解一下。
type slice struct {
array unsafe.Pointer
len int
cap int
}
出於演示目的,這裏有一個關於切片長度和容量概念的示例(如果你已經熟悉這些概念,可以忽略這個示例)。
func main() {
original := []int{0, 1, 2, 3, 4}
s := original[1:2]
fmt.Println(len(s), cap(s))
}
// 1 4
在這個示例中,我們可以看到切片s
等於[]int{1}
,它的容量是從原始數組的索引 1 到索引 4 的部分計算得到的。
底層數組將會改變
需要注意的是,修改切片中的元素有時會影響到底層的數組,但並非總是如此,也不應該依賴這種行爲。
在某些情況下,底層的數組可能會發生改變,導致切片也發生改變。然而,在編寫代碼時不應該依賴這種行爲,因爲它可能會導致意想不到的結果。
func main() {
original := []int{0, 1, 2, 3, 4}
s := original[:]
fmt.Println("Same array:")
s[0] = 100
fmt.Println(original, s)
fmt.Println("Different array:")
s = append(s, 5)
s[0] = 200
fmt.Println(original, s)
}
// Same array:
// [100 1 2 3 4] [100 1 2 3 4]
// Different array:
// [100 1 2 3 4] [200 1 2 3 4 5]
在實際情況中,append()
函數不僅僅是用於添加元素。它還負責處理切片的分配和調整大小。
-
- 它會檢查切片是否有足夠的容量來存儲新的元素。
-
2. 如果容量不足,它會創建一個具有更大容量的新切片,複製原始切片的元素到新切片,並將新切片賦值給原始切片。
-
- 然後,它將新的元素添加到切片中。
這是我用更簡單的方式重寫的append()
函數版本,利用了泛型:
func append[T any](s []T, x ...T) []T {
n := len(s)
maxN := len(s) + len(x)
// If there is not enough capacity, create a new slice with larger capacity
if n+len(x) > cap(s) {
newSlice := make([]T, maxN, maxN*2)
copy(newSlice, s) // Copy the elements from the original slice to the new slice
s = newSlice
}
s = s[:maxN]
copy(s[n:], x)
return s
}
預分配技術
重新調整切片大小在性能和內存方面可能非常昂貴,因爲它需要分配一個新的切片並將所有元素複製過去。
這就是爲什麼在使用切片時,如果我們可以預測它們將保存的元素數量,通常最好進行預分配。這有助於提高性能並防止不必要的內存分配。
“是否可以同時使用 “append()” 和預分配?使用索引賦值可能很麻煩”
是的,這是可能的。
你可以使用make()
函數進行預分配切片,傳入兩個變量,一個用於長度,另一個用於容量,而不是隻傳入一個。這可以消除索引賦值的需要。
func main() {
s := make([]int, 0, 3)
s = append2(s, 1, 2, 3, 4)
fmt.Println(s)
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/g_N2-NIbFTbfQyx9b7HrZg