《Go語言四十二章經(jīng)》第十二章 切片(slice)

作者:李驍

12.1 切片(slice)

切片(slice) 是對(duì)底層數(shù)組一個(gè)連續(xù)片段的引用(該數(shù)組我們稱之為相關(guān)數(shù)組,通常是匿名的),所以切片是一個(gè)引用類型(和數(shù)組不一樣)。切片提供對(duì)該數(shù)組中編號(hào)的元素序列的訪問。 切片類型表示其元素類型的所有數(shù)組切片的集合。未初始化切片的值為nil。

與數(shù)組一樣,切片是可索引的并且具有長度。切片s的長度可以通過內(nèi)置函數(shù)len() 獲取;與數(shù)組不同,切片的長度可能在執(zhí)行期間發(fā)生變化。元素可以通過整數(shù)索引0到len(s)-1來尋址。我們可以把切片看成是一個(gè)長度可變的數(shù)組。

切片提供了計(jì)算容量的函數(shù) cap() ,可以測量切片最大長度。切片的長度永遠(yuǎn)不會(huì)超過它的容量,所以對(duì)于切片 s 來說,這個(gè)不等式永遠(yuǎn)成立:0 <= len(s) <= cap(s)。

一旦初始化,切片始終與保存其元素的基礎(chǔ)數(shù)組相關(guān)聯(lián)。因此,切片會(huì)和與其擁有同一基礎(chǔ)數(shù)組的其他切片共享存儲(chǔ);相比之下,不同的數(shù)組總是代表不同的存儲(chǔ)。

切片下面的數(shù)組可以延伸超過切片的末端。容量是切片長度與切片之外的數(shù)組長度的總和。

使用內(nèi)置函數(shù)make()可以給切片初始化,該函數(shù)指定切片類型和指定長度和可選容量的參數(shù)。

切片與數(shù)組相比較:

優(yōu)點(diǎn)

因?yàn)榍衅且?,所以它們不需要使用額外的內(nèi)存并且比使用數(shù)組更有效率,所以在 Go 代碼中切片比數(shù)組更常用。

聲明切片的格式是: var identifier []type(不需要說明長度)。一個(gè)切片在未初始化之前默認(rèn)為 nil,長度為 0。

切片的初始化格式是:

var slice1 []type = arr1[start:end]

這表示 slice1 是由數(shù)組 arr1 從 start 索引到 end-1 索引之間的元素構(gòu)成的子集(切分?jǐn)?shù)組,start:end 被稱為 slice 表達(dá)式)。

切片也可以用類似數(shù)組的方式初始化:

var x = []int{2, 3, 5, 7, 11}

這樣就創(chuàng)建了一個(gè)長度為 5 的數(shù)組并且創(chuàng)建了一個(gè)相關(guān)切片。

當(dāng)相關(guān)數(shù)組還沒有定義時(shí),我們可以使用 make() 函數(shù)來創(chuàng)建一個(gè)切片,同時(shí)創(chuàng)建好相關(guān)數(shù)組:

var slice1 []type = make([]type, len,cap)

也可以簡寫為 slice1 := make([]type, len),這里 len 是數(shù)組的長度并且也是 slice 的初始長度。cap是容量,其中 cap 是可選參數(shù)。

v := make([]int, 10, 50)

這樣分配一個(gè)有 50 個(gè) int 值的數(shù)組,并且創(chuàng)建了一個(gè)長度為 10,容量為 50 的 切片 v,該切片指向數(shù)組的前 10 個(gè)元素。

以上我們列舉了三種切片初始化方式,這三種方式都比較常用。

如果從數(shù)組或者切片中生成一個(gè)新的切片,我們可以使用下面的表達(dá)式:

a[low : high : max] max-low的結(jié)果表示容量,high-low的結(jié)果表示長度。

a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]

這里t的容量(capacity)是5-1=4 ,長度是2。

如果切片取值時(shí)索引值大于長度會(huì)導(dǎo)致panic錯(cuò)誤發(fā)生,即使容量遠(yuǎn)遠(yuǎn)大于長度也沒有用,如下面代碼所示:

package main

import "fmt"

func main() {
    sli := make([]int, 5, 10)
    fmt.Printf("切片sli長度和容量:%d, %d\n", len(sli), cap(sli))
    fmt.Println(sli)
    newsli := sli[:cap(sli)]
    fmt.Println(newsli)

    var x = []int{2, 3, 5, 7, 11}
    fmt.Printf("切片x長度和容量:%d, %d\n", len(x), cap(x))

    a := [5]int{1, 2, 3, 4, 5}
    t := a[1:3:5] // a[low : high : max]  max-low的結(jié)果表示容量  high-low為長度
    fmt.Printf("切片t長度和容量:%d, %d\n", len(t), cap(t))

    // fmt.Println(t[2]) // panic ,索引不能超過切片的長度
}

程序輸出:
切片sli長度和容量:5, 10
[0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0]
切片x長度和容量:5, 5
切片t長度和容量:2, 4

12.2 切片重組(reslice)

slice1 := make([]type, start_length, capacity)

通過改變切片長度得到新切片的過程稱之為切片重組 reslicing,做法如下:slice1 = slice1[0:end],其中 end 是新的末尾索引(即長度)。

當(dāng)我們?cè)谝粋€(gè)slice基礎(chǔ)上重新劃分一個(gè)slice時(shí),新的slice會(huì)繼續(xù)引用原有slice的數(shù)組。如果你忘了這個(gè)行為的話,在你的應(yīng)用分配大量臨時(shí)的slice用于創(chuàng)建新的slice來引用原有數(shù)據(jù)的一小部分時(shí),會(huì)導(dǎo)致難以預(yù)期的內(nèi)存使用。

package main

import "fmt"

func get() []byte {  
    raw := make([]byte, 10000)
    fmt.Println(len(raw), cap(raw), &raw[0]) // 顯示: 10000 10000 數(shù)組首字節(jié)地址
    return raw[:3]  // 10000個(gè)字節(jié)實(shí)際只需要引用3個(gè),其他空間浪費(fèi)
}

func main() {  
    data := get()
    fmt.Println(len(data), cap(data), &data[0]) // 顯示: 3 10000 數(shù)組首字節(jié)地址
}

為了避免這個(gè)陷阱,我們需要從臨時(shí)的slice中使用內(nèi)置函數(shù)copy(),拷貝數(shù)據(jù)(而不是重新劃分slice)到新切片。

package main

import "fmt"

func get() []byte {
    raw := make([]byte, 10000)
    fmt.Println(len(raw), cap(raw), &raw[0]) // 顯示: 10000 10000 數(shù)組首字節(jié)地址
    res := make([]byte, 3)
    copy(res, raw[:3]) // 利用copy 函數(shù)復(fù)制,raw 可被GC釋放
    return res
}

func main() {
    data := get()
    fmt.Println(len(data), cap(data), &data[0]) // 顯示: 3 3 數(shù)組首字節(jié)地址
}

程序輸出:
10000 10000 0xc000086000
3 3 0xc000050098

append()內(nèi)置函數(shù):

func append(s S, x ...T) S  // T是S元素類型

Append()函數(shù)將 0 個(gè)或多個(gè)具有相同類型 S 的元素追加到切片s后面并且返回新的切片;追加的元素必須和原切片的元素同類型。如果 s 的容量不足以存儲(chǔ)新增元素,append 會(huì)分配新的切片來保證已有切片元素和新增元素的存儲(chǔ)。

因此,append()函數(shù)返回的切片可能已經(jīng)指向一個(gè)不同的相關(guān)數(shù)組了。append()函數(shù)總是返回成功,除非系統(tǒng)內(nèi)存耗盡了。

s0 := []int{0, 0}
s1 := append(s0, 2)                // append 單個(gè)元素     s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)          // append 多個(gè)元素    s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)            // append 一個(gè)切片     s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...)   // append 切片片段    s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

append()函數(shù)操作如果導(dǎo)致分配新的切片來保證已有切片元素和新增元素的存儲(chǔ),也就是返回的切片可能已經(jīng)指向一個(gè)不同的相關(guān)數(shù)組了,那么新的slice已經(jīng)和原來slice沒有任何關(guān)系,即使修改了數(shù)據(jù)也不會(huì)同步。

append()函數(shù)操作后,有沒有生成新的slice需要看原有slice的容量是否足夠。

12.3 陳舊的切片(Stale Slices)

多個(gè)slice可以引用同一個(gè)底層數(shù)組。在某些情況下,在一個(gè)slice中添加新的數(shù)據(jù),在原有數(shù)組無法保持更多新的數(shù)據(jù)時(shí),將導(dǎo)致分配一個(gè)新的數(shù)組。而現(xiàn)在其他的slice還指向老的數(shù)組(和老的數(shù)據(jù))。

上一節(jié)我們也說了:append()函數(shù)操作后,有沒有生成新的slice需要看原有slice的容量是否足夠。

下面,我們看看這個(gè)過程是怎么產(chǎn)生的:

package main

import "fmt"

func main() {
    s1 := []int{1, 2, 3}
    fmt.Println(len(s1), cap(s1), s1) // 輸出 3 3 [1 2 3]
    s2 := s1[1:]
    fmt.Println(len(s2), cap(s2), s2) // 輸出 2 2 [2 3]
    for i := range s2 {
        s2[i] += 20
    }
    // s2的修改會(huì)影響到數(shù)組數(shù)據(jù),s1輸出新數(shù)據(jù)
    fmt.Println(s1) // 輸出 [1 22 23]
    fmt.Println(s2) // 輸出 [22 23]

    s2 = append(s2, 4) // append  s2容量為2,這個(gè)操作導(dǎo)致了slice s2擴(kuò)容,會(huì)生成新的底層數(shù)組。

    for i := range s2 {
        s2[i] += 10
    }
    // s1 的數(shù)據(jù)現(xiàn)在是老數(shù)據(jù),而s2擴(kuò)容了,復(fù)制數(shù)據(jù)到了新數(shù)組,他們的底層數(shù)組已經(jīng)不是同一個(gè)了。
    fmt.Println(len(s1), cap(s1), s1) // 輸出3 3 [1 22 23]
    fmt.Println(len(s2), cap(s2), s2) // 輸出3 4 [32 33 14]
}


程序輸出:
3 3 [1 2 3]
2 2 [2 3]
[1 22 23]
[22 23]
3 3 [1 22 23]
3 4 [32 33 14]

本書《Go語言四十二章經(jīng)》內(nèi)容在github上同步地址:https://github.com/ffhelicopter/Go42
本書《Go語言四十二章經(jīng)》內(nèi)容在簡書同步地址: http://m.itdecent.cn/nb/29056963

雖然本書中例子都經(jīng)過實(shí)際運(yùn)行,但難免出現(xiàn)錯(cuò)誤和不足之處,煩請(qǐng)您指出;如有建議也歡迎交流。
聯(lián)系郵箱:roteman@163.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 數(shù)組Go語言中的數(shù)組是定長的同一類型數(shù)據(jù)的集合,數(shù)組索引是從0開始的。數(shù)組有以下幾種創(chuàng)建方式 以下是一些特殊數(shù)組 ...
    小杰的快樂時(shí)光閱讀 1,970評(píng)論 0 0
  • 出處---Go編程語言 歡迎來到 Go 編程語言指南。本指南涵蓋了該語言的大部分重要特性 Go 語言的交互式簡介,...
    Tuberose閱讀 18,746評(píng)論 1 46
  • 一、Go語言中切片類型出現(xiàn)的原因 切片是一種數(shù)據(jù)類型,這種數(shù)據(jù)類型便于使用和管理數(shù)據(jù)集合。創(chuàng)建一個(gè)100萬個(gè)int...
    碼墨閱讀 1,897評(píng)論 0 1
  • 昨天是考研成績陸續(xù)出來的日子,整好50天。個(gè)中滋味,實(shí)難說清,無數(shù)次的焦灼,假設(shè),心理建設(shè)……卻終究在刷新...
    姜醬犟君閱讀 300評(píng)論 1 1
  • @風(fēng)風(fēng)光光剛剛
    阿寬_7d4c閱讀 425評(píng)論 0 0

友情鏈接更多精彩內(nèi)容