切片長度與容量在 Go 中很常見。切片長度是切片中可用元素的數(shù)量,而切片容量是從切片中第一個元素開始計算的底層數(shù)組中的元素數(shù)量。
Go 中的開發(fā)者經(jīng)?;煜衅L度和容量,或者對它們不夠了解。理解這兩個概念對于高效處理切片的核心操作,比如切片的初始化、使用 append 添加元素、復制或切片操作等,至關(guān)重要。對這些概念的誤解可能導致切片的不合理使用,甚至造成內(nèi)存泄漏。
在 Go 中,切片是由數(shù)組支持的。這意味著切片的數(shù)據(jù)以連續(xù)的方式存儲在數(shù)組數(shù)據(jù)結(jié)構(gòu)中。切片還負責在底層數(shù)組已滿時添加元素,或在幾乎為空時縮減底層數(shù)組。
在內(nèi)部,切片包含指向底層數(shù)組的指針,以及長度和容量。長度表示切片包含的元素數(shù)量,而容量表示底層數(shù)組中的元素數(shù)量,從切片中的第一個元素開始計算。讓我們通過一些示例來更清楚地了解這些概念。首先,讓我們使用給定的長度和容量初始化一個切片:
s := make([]int, 3, 6) // Three-length, six-capacity slice
第一個參數(shù),表示長度,是必須的。但是,第二個參數(shù)表示容量是可選的。圖1展示了此代碼在內(nèi)存中的結(jié)果。
Figure 1 — 一個長度為3、容量為6的切片
在這種情況下,make 創(chuàng)建了一個包含六個元素的數(shù)組(容量)。但由于長度設(shè)置為3,Go 只初始化了前三個元素。另外,因為切片是 []int 類型,所以前三個元素被初始化為 int 類型的零值:0?;疑匾呀?jīng)分配但尚未使用。
如果我們打印這個切片,會得到長度范圍內(nèi)的元素 [0 0 0]。如果我們將 s[1] 設(shè)為1,切片的第二個元素會更新,但不會影響其長度或容量。圖2說明了這一點。
圖2 — 更新切片的第二個元素:s[1] = 1
然而,訪問超出長度范圍之外的元素是被禁止的,即使它在內(nèi)存中已經(jīng)分配。例如,s[4] = 0 會導致以下 panic:
panic: runtime error: index out of range [4] with length 3
我們?nèi)绾问褂们衅S嗟目臻g呢?通過使用內(nèi)置函數(shù) append:
s = append(s, 2)
這段代碼向現(xiàn)有的 s 切片追加了一個新元素。它使用了第一個灰色元素(已分配但尚未使用)來存儲元素2,正如圖3所示。
圖3 — 向 s 切片追加一個元素
切片的長度從3更新為4,因為現(xiàn)在切片包含了四個元素。現(xiàn)在,如果我們再添加三個元素以至于后臺數(shù)組不夠大,會發(fā)生什么?
s = append(s, 3, 4, 5)fmt.Println(s)
如果我們運行這段代碼,會看到切片能夠滿足我們的請求:
[0 1 0 2 3 4 5]
因為數(shù)組是一個固定大小的結(jié)構(gòu),在第4個元素之前,它能夠存儲新的元素。當我們想要插入第5個元素時,數(shù)組已經(jīng)滿了:Go 內(nèi)部會創(chuàng)建另一個數(shù)組,將所有元素復制過去,然后再插入第5個元素。圖4展示了這個過程。
圖4 — 因為初始的后臺數(shù)組已滿,Go 創(chuàng)建了另一個數(shù)組并復制了所有元素。
現(xiàn)在切片引用了新的后臺數(shù)組。之前的后臺數(shù)組會怎樣呢?如果它不再被引用,它最終會被垃圾收集器(GC)釋放,如果它是在堆上分配的話(我們在錯誤#95 “不理解堆棧與堆的區(qū)別”中討論了堆內(nèi)存,并在錯誤#99 “不理解GC的工作原理”中討論了GC的工作原理)。
對切片進行切片操作會發(fā)生什么?切片是對數(shù)組或切片進行的操作,提供了一個左閉右開的范圍;第一個索引是包括的,而第二個索引是排除的。以下示例展示了影響,并在圖5中顯示了內(nèi)存中的結(jié)果:
s1 := make([]int, 3, 6) // Three-length, six-capacity slices2 := s1[1:3] // Slicing from indices 1 to 3
圖5 — 切片 s1 和 s2 引用相同的后臺數(shù)組,但長度和容量不同
首先,s1 是一個長度為3、容量為6的切片。當通過對 s1 進行切片創(chuàng)建 s2 時,兩個切片都引用同一個后臺數(shù)組。但是,s2 從不同的索引開始,即索引1。因此,它的長度和容量(長度為2,容量為5)與 s1 不同。如果我們更新 s1[1] 或 s2[0],則更改會作用于同一個數(shù)組,因此在兩個切片中都是可見的,如圖6所示。
圖6 — 因為 s1 和 s2 共享同一個數(shù)組,更新共同的元素會使兩個切片中的更改都可見
現(xiàn)在,如果我們向 s2 添加一個元素會發(fā)生什么?以下代碼會同時改變 s1 嗎?
s2 = append(s2, 2)
共享的后臺數(shù)組被修改,但只有 s2 的長度發(fā)生了變化。圖7展示了向 s2 添加元素的結(jié)果。
圖7 — 向 s2 添加元素
s1 仍然是一個長度為3、容量為6的切片。因此,如果我們打印 s1 和 s2,添加的元素只會在 s2 中可見:
s1=[0 1 0], s2=[1 0 2]
很重要理解這種行為,這樣我們在使用 append 時就不會形成錯誤的假設(shè)。
注意: 在這些示例中,后臺數(shù)組是內(nèi)部的,不直接對 Go 開發(fā)者可見。唯一的例外是從現(xiàn)有數(shù)組切片創(chuàng)建切片。
還有最后一件事需要注意:如果我們不斷向 s2 中添加元素,直到后臺數(shù)組滿為止,內(nèi)存狀態(tài)會是怎樣的?讓我們再添加三個元素,以便后臺數(shù)組沒有足夠的容量:
s2 = append(s2, 3)s2 = append(s2, 4) // At this stage, the backing is already fulls2 = append(s2, 5)
這段代碼導致創(chuàng)建了另一個后臺數(shù)組。圖 8 展示了內(nèi)存中的結(jié)果。
圖 8 — 向 s2 添加元素直到后臺數(shù)組已滿
s1 和 s2 現(xiàn)在引用兩個不同的數(shù)組。由于 s1 仍然是一個三長度、六容量的切片,它仍然有一些可用緩沖區(qū),因此它繼續(xù)引用最初的數(shù)組。而且,新的后臺數(shù)組是通過從 s2 的第一個索引復制初始數(shù)組而生成的。這就是為什么新數(shù)組從元素 1 開始,而不是 0。
總結(jié)一下,切片長度 是切片中可用元素的數(shù)量,而 切片容量 是后臺數(shù)組中的元素數(shù)量。向一個已滿的切片(長度 == 容量)添加元素會導致創(chuàng)建一個新的后臺數(shù)組,將之前數(shù)組中的所有元素復制到新數(shù)組中,并更新切片指向新數(shù)組。
本文鏈接:http://www.www897cc.com/showinfo-26-34929-0.htmlGo 中切片(Slice)的長度與容量
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 理解C++之構(gòu)造函數(shù)