Go語言中的切片類型

圖文無關(guān)

本文翻譯自Andrew Gerrand的博文 https://blog.golang.org/go-slices-usage-and-internals

前言

Go語言中提供了的切片類型琢感,方便使用者處理類型數(shù)據(jù)序列宙拉。
切片有點像其他語言中的數(shù)組喉镰,并且提供了一些額外的屬性。

數(shù)組

Go語言自帶了數(shù)組類型宣蔚,而切片類型是基于數(shù)組類型的抽象。因此脚囊,要理解切片類型郭怪,我們必須首先理解數(shù)組。
定義一個數(shù)組時广辰,需要指定數(shù)組長度和數(shù)組中元素的類型暇矫,比如說 [4]int定義了長度為4的數(shù)組,其中的元素類型為int择吊。一個數(shù)組的長度是固定的李根;長度是數(shù)組類型的一部分([4]int[5]int就是兩個不同的類型)。數(shù)組以通常的方式進(jìn)行索引干发,所以表達(dá)式s[n]能訪問到數(shù)組s的第n個元素(從0開始)朱巨。

var a [4]int
a[0] = 1
i := a[0]
// i == 1

在沒有顯式初始化時,數(shù)組默認(rèn)會將元素初始化為0枉长。

// a[2] == 0

在內(nèi)存中,[4]int表示為順序排列的4個整數(shù)值

內(nèi)存中的 [4]int

Go語言中的數(shù)組是一個值琼讽。數(shù)組變量表示整個數(shù)組,而不是指向數(shù)組第一個元素的指針(就像C語言那樣)钻蹬。這就意味著吼蚁,將一個數(shù)組當(dāng)作一個參數(shù)傳遞時,會完全拷貝數(shù)組中的內(nèi)容(如果不想完全拷貝數(shù)組问欠,可以傳一個指向數(shù)組的指針)肝匆。
可以把數(shù)組當(dāng)成這樣一種結(jié)構(gòu),它具有索引顺献,有著固定的大小旗国,可以用來存儲不同類型的元素。

一個字符串?dāng)?shù)組可以這樣定義

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

或者讓編譯器來確定數(shù)組的長度

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

上面的兩個例子中注整,b的類型都是 [2]string能曾。

切片類型

數(shù)組類型是很有用的度硝,但是不太靈活,所以Go代碼中很少看到它們寿冕。但是切片類型卻是很常見的蕊程,因為它基于數(shù)組類型提供了強(qiáng)大的功能和開發(fā)便利。

切片類型的定義如[]T驼唱,其中T是切片中元素的類型藻茂。與數(shù)組類型不同,切片類型沒有固定的長度玫恳。

定義一個切片和定義一個數(shù)組的語法相似辨赐,唯一的不同是不需要定義切片長度。

letters := []string{"a", "b", "c", "d"}

可以用內(nèi)置的make關(guān)鍵字定義一個切片

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

其中T表示切片中元素的類型纽窟。make函數(shù)接受元素類型肖油,長度和容量(可選)作為傳入?yún)?shù)。當(dāng)被調(diào)用時臂港,make分配一個數(shù)組森枪,并且返回一個指向該數(shù)組的切片。

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

如果沒有傳入cap參數(shù)审孽,它的默認(rèn)值是傳入的長度县袱。這是上面代碼的一個簡潔版本。

s := make([]byte, 5)

可以使用內(nèi)置的lencap函數(shù)檢查切片的長度和容量佑力。

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

下面兩個章節(jié)將討論長度和容量的關(guān)系式散。
切片的零值為nil。對一個值為nil的切片來說打颤,lencap會返回0暴拄。

可以通過“切”一個數(shù)組或者是切片,來生成新的切片编饺。這個過程通過指定兩個索引的半開范圍來完成乖篷,兩個索引之間用冒號隔開。舉個例子透且,b[1:4]會返回一個新的切片撕蔼,包含的元素為b中的第1到第3的元素

b ;= []byte{'g', 'o', 'l', 'a', 'n', 'g'}
// b[1:4] == []byte{'o', 'l', 'a'}  和b中的元素占用同一塊內(nèi)存

起始和結(jié)束索引是可選的,其默認(rèn)值分別為0和切片的長度

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

基于數(shù)組創(chuàng)建切片語法與上面的類似秽誊。

x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:]     // s為指向x的引用

探尋切片內(nèi)部

切片是數(shù)組段的描述符鲸沮。它包含了一個指向數(shù)組的指針,數(shù)據(jù)段的長度和容量锅论。

切片結(jié)構(gòu)

通過s := make([]byte, 5)方式聲明的切片結(jié)構(gòu)如下

s結(jié)構(gòu)

長度是切片指向內(nèi)容中元素的個數(shù)讼溺。容量是底層數(shù)組中的元素個數(shù)(從切片指向的元素開始計數(shù))。長度和容量的區(qū)別會在下面的例子中解釋棍厌。

s進(jìn)行切片肾胯,觀察下面切片和數(shù)組的關(guān)系

s = s[2:4]
切片和數(shù)組

切片操作并不會拷貝s中的數(shù)據(jù)竖席,而是創(chuàng)建一個新的切片指向原來的數(shù)組,這讓切片操作就像操作數(shù)組索引一樣高效敬肚。因此毕荐,對切片的元素進(jìn)行修改,會修改原始切片的元素艳馒。

d := []byte{'r', 'o', 'a', 'd'}
e := d[2:]
// e == []byte{'a', 'd'}
e[1] = 'm'
// e == []byte{'a', 'm'}
// d == []byte{'r', 'o', 'a', 'm'}

之前的操作中憎亚,將s進(jìn)行切片,其長度小于容量∨浚現(xiàn)在對其重新切片

s = s[:cap(s)]
切片后結(jié)果

切片的長度不能大于其容量第美。這樣做會導(dǎo)致一個runtime panic,就像對切片或者數(shù)組進(jìn)行越界訪問一樣陆爽。

增加切片容量

要增加切片的容量什往,必須新建一個容量更大的切片,然后將之前的切片的數(shù)據(jù)拷貝到新的切片中慌闭。這也是其他語言實現(xiàn)動態(tài)數(shù)組的方式别威。下面的例子,新建一個容量是s兩倍的切片t驴剔,然后將s的數(shù)據(jù)拷貝到t中省古,最后將t賦值給s:

t := make([]byte, len(s)m (cap(s)+1)*2) // +1對應(yīng) cap(s) == 0的情況
for i := range s {
     t[i] = s[i]
}
s = t

使用內(nèi)置的copy函數(shù)可以簡化上面的代碼。顧名思義丧失,copy將數(shù)據(jù)從一個切片拷貝到另一個切片豺妓,并返回拷貝元素的數(shù)量。
語法如下:

func copy(dst, src []T) int

函數(shù)copy 支持兩個不同長度切片之間的拷貝布讹。另外琳拭,copy可以處理源和目的切片指向相同底層數(shù)組的情況,正確處理重疊的切片描验。

簡化上面的代碼

t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t

一個常見的操作是在切片的末尾添加一個元素臀栈。下面的函數(shù)在一個切片的末尾增加一個元素,在容量不夠的情況下增加切片的容量挠乳,并且返回更新后的切片

func AppendByte(slice []byte, data ...type) []byte {
    m := len(slice)
    n := m + len(data)
    if n > cap(slice) {
        newSlice := make([]byte, (n+1)*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:n]
    copy(slice[m:n], data)
    return slice
}

下面代碼展示了AppendByte的用法

p := []byte{2, 3, 5}
p = AppendByte(p, 7, 11, 13)
// p == []byte{2, 3, 5, 7, 11, 13}

AppendByte這樣的函數(shù)是很有用的,因為它能完全控制切片大小姑躲∷铮可以根據(jù)程序?qū)崿F(xiàn)的功能,分配更大黍析,更小的空間卖怜,或者為分配的空間設(shè)置一個上限。

但是大多數(shù)程序并不需要這樣的完全控制阐枣,這時候Go語言內(nèi)置的append函數(shù)就派上用場了马靠。它的語法如下

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

函數(shù)appendx添加到s末尾奄抽,如果需要就擴(kuò)展s的容量。

a := make([]int, 1)
// a == []int{0}
a = append(a, 1, 2, 3)
// a == []int{0, 1, 2, 3}

使用...將一個切片添加到另外一個切片末尾

a := []string{"John", "Paul"}
b := []string{"George", "Ringo", "Pete"}
a = append(a, b...) // 等同于append(a, b[0], b[1], b[2])
//  a == []string{"John", "Paul", "George", "Ringo", "Pete"}

因為零值的切片(nil)和長度為0的切片相似甩鳄,可以聲明一個切片變量逞度,然后在循環(huán)中在其末尾添加元素。

// 通過fn篩選出s中的元素
func Filter(s []int, fn func(int) bool) []int {
    var p []int // == nil
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

可能遇到的坑

如前面提到的妙啃,對一個切片進(jìn)行切片不會拷貝切片指向的數(shù)組档泽。這個數(shù)組會一致保存在內(nèi)存中,直到不再被引用揖赴。有時這樣會導(dǎo)致程序會將所有的數(shù)據(jù)保存在內(nèi)存中馆匿,即使只有一小部分?jǐn)?shù)據(jù)是被需要的。

舉個例子燥滑,下面FindDigits函數(shù)會將一個文件中的內(nèi)容保存在內(nèi)存中渐北,搜索第一組連續(xù)數(shù)字,并將它們作為新的切片返回铭拧。

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

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

上面的代碼能完成所需要的功能赃蛛,但是返回的[]byte切片指向的是保存了文件所有數(shù)據(jù)的數(shù)組。只要這個切片一直保留著羽历,垃圾回收將不能釋放保存了所有數(shù)據(jù)的數(shù)組焊虏。文件一小部分有用的數(shù)據(jù)將會讓所有的數(shù)據(jù)一直保存在內(nèi)存中。

要解決這個問題秕磷,可以先將有用的數(shù)據(jù)先保存到一個新的切片诵闭,然后返回新的切片。

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)容合作請聯(lián)系作者
  • 序言:七十年代末澎嚣,一起剝皮案震驚了整個濱河市疏尿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌易桃,老刑警劉巖褥琐,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異晤郑,居然都是意外死亡敌呈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門造寝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來磕洪,“玉大人,你說我怎么就攤上這事诫龙∥鱿裕” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵签赃,是天一觀的道長谷异。 經(jīng)常有香客問我分尸,道長,這世上最難降的妖魔是什么歹嘹? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任箩绍,我火速辦了婚禮,結(jié)果婚禮上荞下,老公的妹妹穿的比我還像新娘伶选。我一直安慰自己,他們只是感情好尖昏,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布仰税。 她就那樣靜靜地躺著,像睡著了一般抽诉。 火紅的嫁衣襯著肌膚如雪陨簇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天迹淌,我揣著相機(jī)與錄音河绽,去河邊找鬼。 笑死唉窃,一個胖子當(dāng)著我的面吹牛耙饰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纹份,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼苟跪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蔓涧?” 一聲冷哼從身側(cè)響起件已,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎元暴,沒想到半個月后篷扩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡茉盏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年鉴未,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸠姨。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡歼狼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出享怀,到底是詐尸還是另有隱情,我是刑警寧澤趟咆,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布添瓷,位于F島的核電站梅屉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鳞贷。R本人自食惡果不足惜坯汤,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搀愧。 院中可真熱鬧惰聂,春花似錦、人聲如沸咱筛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迅箩。三九已至溉愁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饲趋,已是汗流浹背拐揭。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留奕塑,地道東北人堂污。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像龄砰,于是被迫代替她去往敵國和親盟猖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 切片(slice)是 Golang 中一種比較特殊的數(shù)據(jù)結(jié)構(gòu)寝贡,這種數(shù)據(jù)結(jié)構(gòu)更便于使用和管理數(shù)據(jù)集合扒披。切片是圍繞動態(tài)...
    小孩真笨閱讀 1,079評論 0 1
  • 一、Go語言中切片類型出現(xiàn)的原因 切片是一種數(shù)據(jù)類型圃泡,這種數(shù)據(jù)類型便于使用和管理數(shù)據(jù)集合碟案。創(chuàng)建一個100萬個int...
    碼墨閱讀 1,794評論 0 1
  • 最近面試較多,但其實很多內(nèi)容自己也不太會颇蜡,所以有了自問自答的環(huán)節(jié)价说。a.什么是BFC浮動元素和絕對定位元素,非塊級盒...
    等花開_8e16閱讀 394評論 0 0
  • 你有沒有想過我也很好 只是你沒發(fā)現(xiàn)
    wsno閱讀 276評論 0 0
  • 今天關(guān)于身上的紅點风秤,有了新的解釋鳖目,那就是被蟲子的體液毒到了。因為根據(jù)媽媽的理論缤弦,過敏的話應(yīng)該全身長紅點领迈。
    木衛(wèi)33閱讀 111評論 0 0