golang slice理解

介紹

Go的切片類型提供了一種方便有效的處理類型數(shù)據(jù)序列的方法。切片類似于其他語言中的數(shù)組,但具有一些不尋常的屬性。本文將介紹什么是切片并且如何使用他們

數(shù)組

切片類型是構(gòu)建在Go的數(shù)組類型之上的抽象,因此為了理解切片,我們必須首先理解數(shù)組。

數(shù)組類型定義指定長(zhǎng)度和元素類型。例如,類型[4]int表示四個(gè)整數(shù)的數(shù)組。數(shù)組的大小是固定的; 它的長(zhǎng)度是它的類型的一部分([4]int并且[5]int是不同的,不兼容的類型)。數(shù)組可以通常的方式編入索引,因此表達(dá)式s[n]從零開始訪問第n個(gè)元素。

package main

import "fmt"

func main() {
    var a [4]int
    a[0] = 1
    fmt.Println(a[0], a[2])

}
output:
1 0

數(shù)組不需要顯式初始化; 數(shù)組的零值是一個(gè)現(xiàn)成的數(shù)組,其元素本身為零.
內(nèi)存中的表示[4]int只是順序排列的四個(gè)整數(shù)值:


Go的數(shù)組是值。數(shù)組變量表示整個(gè)數(shù)組; 它不是指向第一個(gè)數(shù)組元素的指針(如C中的情況)。這意味著當(dāng)您分配或傳遞數(shù)組值時(shí),您將復(fù)制其內(nèi)容。(為了避免復(fù)制,你可以傳遞一個(gè)指向數(shù)組的指針,但那是一個(gè)指向數(shù)組的指針,而不是一個(gè)數(shù)組。)一種思考數(shù)組的方法是作為一種結(jié)構(gòu),但有索引而不是命名字段:一個(gè)固定的-size復(fù)合值。
數(shù)組可以這么定義:

b := [2]string{"Penn", "Teller"}

你也可以這么定義:

b := [...]string{"Penn", "Teller"}

兩種定義方式變量都是[2]string類型

slices

數(shù)組有它們的位置,但它們有點(diǎn)不靈活,所以你不會(huì)在Go代碼中經(jīng)??吹剿鼈?。然而,切片無處不在。它們以陣列為基礎(chǔ),提供強(qiáng)大的功能和便利性。

切片的類型規(guī)范是[]T,切片T元素的類型。與數(shù)組類型不同,切片類型沒有指定的長(zhǎng)度。

切片文字聲明就像數(shù)組文字一樣,除了省略元素?cái)?shù):
可以使用調(diào)用的內(nèi)置函數(shù)make創(chuàng)建切片,

func make([] T,len,cap)[] T

其中T代表要?jiǎng)?chuàng)建的切片的元素類型。該make函數(shù)采用類型,長(zhǎng)度和可選容量。調(diào)用時(shí),make分配一個(gè)數(shù)組并返回引用該數(shù)組的切片。

var s [] byte
s = make([] byte,5,5)
// s == [] byte {0,0,0,0,0}

省略capacity參數(shù)時(shí),默認(rèn)為指定的長(zhǎng)度。這是相同代碼的更簡(jiǎn)潔版本:

s:= make([] byte,5)

可以使用內(nèi)置len和cap函數(shù)檢查切片的長(zhǎng)度和容量。

len(s)== 5
cap(s)== 5

切片長(zhǎng)度和容量的關(guān)系

還可以通過“切片”現(xiàn)有切片或陣列來形成切片。通過指定半開放范圍來完成切片,其中兩個(gè)索引用冒號(hào)分隔。例如,表達(dá)式b[1:4]創(chuàng)建一個(gè)包含元素1到3的b切片(結(jié)果切片的索引將為0到2)。

b:= [] byte {'g','o','l','a','n','g'}
// b [1:4] == [] byte {'o','l','a'},與b共享相同的存儲(chǔ)空間

切片表達(dá)式的開始和結(jié)束索引是可選的; 它們分別默認(rèn)為零和切片長(zhǎng)度:

// b [:2] == [] byte {'g','o'}
// b [2:] == [] byte {'l','a','n','g'}
// b [:] == b

這也是給定數(shù)組創(chuàng)建切片的語法:

x:= [3] string {“Лайка”,“Белка”,“Стрелка”}
s:= x [:] //引用x存儲(chǔ)的切片

切片內(nèi)部

切片是數(shù)組段的描述符。它由指向數(shù)組的指針,段的長(zhǎng)度及其容量(段的最大長(zhǎng)度)組成
[站外圖片上傳中...(image-2fae1-1563440373053)]
我們s之前創(chuàng)建的變量的make([]byte, 5)結(jié)構(gòu)如下:
[站外圖片上傳中...(image-2218ae-1563440373054)]
長(zhǎng)度是切片引用的元素?cái)?shù)。容量是底層數(shù)組中元素的數(shù)量(從切片指針引用的元素開始)。在我們通過接下來的幾個(gè)例子時(shí),將明確區(qū)分長(zhǎng)度和容量。

在切片時(shí)s,觀察切片數(shù)據(jù)結(jié)構(gòu)的變化及其與底層數(shù)組的關(guān)系:

s = s [2:4]

[站外圖片上傳中...(image-6f6534-1563440373054)]
切片不會(huì)復(fù)制切片的數(shù)據(jù)。它創(chuàng)建一個(gè)指向原始數(shù)組的新切片值。這使切片操作與操作數(shù)組索引一樣高效。因此,修改重新切片的元素(而不是切片本身)會(huì)修改原始切片的元素:

    d := []byte{'r', 'o', 'a', 'd'}
    e := d[2:]
    fmt.Println(e)
    fmt.Println(d)
    e[1] = 'm'
    fmt.Println(e)
    fmt.Println(d)
output:
[97 100]
[114 111 97 100]
[97 109]
[114 111 97 109]

切片不能超出其容量。嘗試這樣做會(huì)導(dǎo)致運(yùn)行時(shí)出現(xiàn)混亂,就像在切片或數(shù)組的邊界之外進(jìn)行索引一樣。類似地,切片不能在零以下重新切片以訪問數(shù)組中的早期元素

生長(zhǎng)切片(復(fù)制和追加功能)

要增加切片的容量,必須創(chuàng)建一個(gè)新的更大的切片并將原始切片的內(nèi)容復(fù)制到切片中

        s := make([]byte, 1)
    fmt.Println(s, len(s), cap(s))
    t := make([]byte, len(s), (cap(s)+1)*2)
    //for i := range s {
    //  t[i] = s[i]
    //}
    copy(t, s)
    fmt.Println(t, len(t), cap(t))
    output:
    [0] 1 1
    [0] 1 4

擴(kuò)容后,使用range賦值還是copy函數(shù)兩種方式是對(duì)等的

func copy(dst,src [] T)int

當(dāng)然copy方法簡(jiǎn)化了賦值的步驟
一個(gè)常用的操作是動(dòng)態(tài)增加容量,舉個(gè)例子:

func AppendByte(slice []byte, data ...byte) []byte {
    m := len(slice)
    n := m + len(data)
    if n > cap(slice) { // if necessary, reallocate
        // allocate double what's needed, for future growth.
        newSlice := make([]byte, (n+1)*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:n]
    copy(slice[m:n], data)
    return slice
}

func main() {
    a := []byte{1, 2}
    fmt.Println(a)
    a = AppendByte(a, 3, 4, 6)
    fmt.Println(a)
}
output:
[1 2]
[1 2 3 4 6]

上面的例子顯示如果容量不夠,以兩倍容量增加,當(dāng)然算法都是根據(jù)需求自定義

不過大多數(shù)情況不需要太復(fù)雜操作,可以通過append函數(shù)將新元素加入到slice尾部,如果slice容量不夠,系統(tǒng)會(huì)動(dòng)態(tài)添加容量

func append(s []T, x ...T) []T

舉例如下:

a := make([]int, 1)
fmt.Println(a, len(a), cap(a))
a = append(a, 1, 2, 3)
fmt.Println(a, len(a), cap(a))
output:
[0] 1 1
[0 1 2 3] 4 4

如果想通過append添加另一個(gè)slice,可以如下操作:

 b := []string{"John", "Paul"}
fmt.Println(b, len(b), cap(b))
c := []string{"George", "Ringo", "Pete"}
b = append(b, c...)
fmt.Println(b, len(b), cap(b))
var d []string
b = append(b, d...)
fmt.Println(b, len(b), cap(b), d, d == nil)
output:
[John Paul] 2 2
[John Paul George Ringo Pete] 5 5
[John Paul George Ringo Pete] 5 5 [] true

nil slice也可以進(jìn)行append

一個(gè)可能的陷阱

如前所述,重新切片切片不會(huì)復(fù)制底層數(shù)組。完整數(shù)組將保留在內(nèi)存中,直到不再引用它為止。偶爾這會(huì)導(dǎo)致程序在只需要一小部分?jǐn)?shù)據(jù)時(shí)將所有數(shù)據(jù)保存在內(nèi)存中。

例如,此FindDigits函數(shù)將文件加載到內(nèi)存中,并在其中搜索第一組連續(xù)數(shù)字?jǐn)?shù)字,并將其作為新切片返回。

var digitRegexp = regexp.MustCompile(“[0-9] +”)

func FindDigits(filename string)[] byte { 
    b,_:= ioutil.ReadFile(filename)
    return digitRegexp.Find(b)
}

此代碼的行為與廣告一樣,但返回的[]byte指向包含整個(gè)文件的數(shù)組。由于切片引用原始數(shù)組,只要切片保持在垃圾收集器周圍就不能釋放數(shù)組; 文件中幾個(gè)有用的字節(jié)將整個(gè)內(nèi)容保存在內(nèi)存中。

要解決此問題,可以在返回之前將數(shù)據(jù)復(fù)制到新切片:

func CopyDigits(filename string)[] byte { 
    b,_:= ioutil.ReadFile(filename)
    b = digitRegexp.Find(b)
    c:= make([] byte,len(b))
    copy(c,b)
    return c 
}
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 切片(slice)是 Golang 中一種比較特殊的數(shù)據(jù)結(jié)構(gòu),這種數(shù)據(jù)結(jié)構(gòu)更便于使用和管理數(shù)據(jù)集合。切片是圍繞動(dòng)態(tài)...
    小孩真笨閱讀 1,222評(píng)論 0 1
  • 切片(slice)是 Golang 中一種比較特殊的數(shù)據(jù)結(jié)構(gòu),這種數(shù)據(jù)結(jié)構(gòu)更便于使用和管理數(shù)據(jù)集合。切片是圍繞動(dòng)態(tài)...
    51reboot閱讀 28,866評(píng)論 2 10
  • 線性結(jié)構(gòu)是計(jì)算機(jī)最常用的數(shù)據(jù)結(jié)構(gòu)之一。無論是數(shù)組(arrary)還是鏈表(list),在編程中不可或缺。golan...
    _二少爺閱讀 6,761評(píng)論 5 13
  • 切片是 Go 中的一種基本的數(shù)據(jù)結(jié)構(gòu),使用這種結(jié)構(gòu)可以用來管理數(shù)據(jù)集合。切片的設(shè)計(jì)想法是由動(dòng)態(tài)數(shù)組概念而來,為了開...
    一縷殤流化隱半邊冰霜閱讀 11,466評(píng)論 21 55
  • 云繞青山翻林中,霧騰空穴話朦朧,山澗溪水忙湍急,草木如洗碧翠玉,斜風(fēng)細(xì)雨織惆悵,幽風(fēng)清冷泣哀怨。人間七月陰雨天,憶...
    詩(shī)心不改閱讀 320評(píng)論 1 7

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