主頁 > 知識庫 > Golang中切片的用法與本質(zhì)詳解

Golang中切片的用法與本質(zhì)詳解

熱門標(biāo)簽:網(wǎng)站排名優(yōu)化 AI電銷 鐵路電話系統(tǒng) Linux服務(wù)器 服務(wù)外包 百度競價排名 呼叫中心市場需求 地方門戶網(wǎng)站

前言

Go 數(shù)組的長度不可改變,在特定場景中這樣的集合就不太適用,Go中提供了一種靈活,功能強悍的內(nèi)置類型切片("動態(tài)數(shù)組"),與數(shù)組相比切片的長度是不固定的,可以追加元素,在追加時可能使切片的容量增大

Go的切片類型為處理同類型數(shù)據(jù)序列提供一個方便而高效的方式。 切片有些類似于其他語言中的數(shù)組,但是有一些不同尋常的特性。 本文將深入切片的本質(zhì),并講解它的用法。

數(shù)組

Go的切片是在數(shù)組之上的抽象數(shù)據(jù)類型,因此在了解切片之前必須要先理解數(shù)組。

數(shù)組類型定義了長度和元素類型。例如, [4]int 類型表示一個四個整數(shù)的數(shù)組。 數(shù)組的長度是固定的,長度是數(shù)組類型的一部分(  [4]int 和  [5]int 是完全不同的類型)。 數(shù)組可以以常規(guī)的索引方式訪問,表達式  s[n] 訪問數(shù)組的第 n 個元素。

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

數(shù)組不需要顯式的初始化;數(shù)組的零值是可以直接使用的,數(shù)組元素會自動初始化為其對應(yīng)類型的零值:

// a[2] == 0, int 類型的零值

類型 [4]int 對應(yīng)內(nèi)存中四個連續(xù)的整數(shù):


Go的數(shù)組是值語義。一個數(shù)組變量表示整個數(shù)組,它不是指向第一個元素的指針(不像 C 語言的數(shù)組)。 當(dāng)一個數(shù)組變量被賦值或者被傳遞的時候,實際上會復(fù)制整個數(shù)組。 (為了避免復(fù)制數(shù)組,你可以傳遞一個指向數(shù)組的指針,但是數(shù)組指針并不是數(shù)組。) 可以將數(shù)組看作一個特殊的struct,結(jié)構(gòu)的字段名對應(yīng)數(shù)組的索引,同時成員的數(shù)目固定。

數(shù)組的字面值像這樣:

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

當(dāng)然,也可以讓編譯器統(tǒng)計數(shù)組字面值中元素的數(shù)目:

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

這兩種寫法, b 都是對應(yīng)  [2]string 類型。

切片

數(shù)組雖然有適用它們的地方,但是數(shù)組不夠靈活,因此在Go代碼中數(shù)組使用的并不多。 但是,切片則使用得相當(dāng)廣泛。切片基于數(shù)組構(gòu)建,但是提供更強的功能和便利。

切片類型的寫法是 []T ,  T 是切片元素的類型。和數(shù)組不同的是,切片類型并沒有給定固定的長度。

切片的字面值和數(shù)組字面值很像,不過切片沒有指定元素個數(shù):

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

切片可以使用內(nèi)置函數(shù) make 創(chuàng)建,函數(shù)簽名為:

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

其中T代表被創(chuàng)建的切片元素的類型。函數(shù) make 接受一個類型、一個長度和一個可選的容量參數(shù)。 調(diào)用  make 時,內(nèi)部會分配一個數(shù)組,然后返回數(shù)組對應(yīng)的切片。

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

當(dāng)容量參數(shù)被忽略時,它默認為指定的長度。下面是簡潔的寫法:

s := make([]byte, 5)

可以使用內(nèi)置函數(shù) len 和  cap 獲取切片的長度和容量信息。

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

接下來的兩個小節(jié)將討論長度和容量之間的關(guān)系。

切片的零值為 nil 。對于切片的零值,  len 和  cap 都將返回0。

切片也可以基于現(xiàn)有的切片或數(shù)組生成。切分的范圍由兩個由冒號分割的索引對應(yīng)的半開區(qū)間指定。 例如,表達式 b[1:4] 創(chuàng)建的切片引用數(shù)組  b 的第1到3個元素空間(對應(yīng)切片的索引為0到2)。

b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
// b[1:4] == []byte{'o', 'l', 'a'}, sharing the same storage as b

切片的開始和結(jié)束的索引都是可選的;它們分別默認為零和數(shù)組的長度。

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

下面語法也是基于數(shù)組創(chuàng)建一個切片:

x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:] // a slice referencing the storage of x

切片的內(nèi)幕

一個切片是一個數(shù)組片段的描述。它包含了指向數(shù)組的指針,片段的長度, 和容量(片段的最大長度)。


前面使用 make([]byte, 5) 創(chuàng)建的切片變量  s 的結(jié)構(gòu)如下:


長度是切片引用的元素數(shù)目。容量是底層數(shù)組的元素數(shù)目(從切片指針開始)。 關(guān)于長度和容量和區(qū)域?qū)⒃谙乱粋€例子說明。

我們繼續(xù)對 s 進行切片,觀察切片的數(shù)據(jù)結(jié)構(gòu)和它引用的底層數(shù)組:

s = s[2:4]

切片操作并不復(fù)制切片指向的元素。它創(chuàng)建一個新的切片并復(fù)用原來切片的底層數(shù)組。 這使得切片操作和數(shù)組索引一樣高效。因此,通過一個新切片修改元素會影響到原始切片的對應(yīng)元素。

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'}

前面創(chuàng)建的切片 s 長度小于它的容量。我們可以增長切片的長度為它的容量:

s = s[:cap(s)]

切片增長不能超出其容量。增長超出切片容量將會導(dǎo)致運行時異常,就像切片或數(shù)組的索引超 出范圍引起異常一樣。同樣,不能使用小于零的索引去訪問切片之前的元素。

切片的生長(copy and append 函數(shù))

要增加切片的容量必須創(chuàng)建一個新的、更大容量的切片,然后將原有切片的內(nèi)容復(fù)制到新的切片。 整個技術(shù)是一些支持動態(tài)數(shù)組語言的常見實現(xiàn)。下面的例子將切片 s 容量翻倍,先創(chuàng)建一個2倍 容量的新切片  t ,復(fù)制  s 的元素到  t ,然后將  t 賦值給  s :

t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0
for i := range s {
  t[i] = s[i]
}
s = t

循環(huán)中復(fù)制的操作可以由 copy 內(nèi)置函數(shù)替代。copy 函數(shù)將源切片的元素復(fù)制到目的切片。 它返回復(fù)制元素的數(shù)目。

func copy(dst, src []T) int

copy 函數(shù)支持不同長度的切片之間的復(fù)制(它只復(fù)制較短切片的長度個元素)。 此外,  copy 函數(shù)可以正確處理源和目的切片有重疊的情況。

使用 copy 函數(shù),我們可以簡化上面的代碼片段:

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

一個常見的操作是將數(shù)據(jù)追加到切片的尾部。下面的函數(shù)將元素追加到切片尾部, 必要的話會增加切片的容量,最后返回更新的切片:

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
}

下面是 AppendByte 的一種用法:

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

類似 AppendByte 的函數(shù)比較實用,因為它提供了切片容量增長的完全控制。 根據(jù)程序的特點,可能希望分配較小的活較大的塊,或則是超過某個大小再分配。

但大多數(shù)程序不需要完全的控制,因此Go提供了一個內(nèi)置函數(shù) append , 用于大多數(shù)場合;它的函數(shù)簽名:

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

append 函數(shù)將  x 追加到切片  s 的末尾,并且在必要的時候增加容量。

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

如果是要將一個切片追加到另一個切片尾部,需要使用 ... 語法將第2個參數(shù)展開為參數(shù)列表。

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

由于切片的零值 nil 用起來就像一個長度為零的切片,我們可以聲明一個切片變量然后在循環(huán) 中向它追加數(shù)據(jù):

// Filter returns a new slice holding only
// the elements of s that satisfy f()
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
}

可能的“陷阱”

正如前面所說,切片操作并不會復(fù)制底層的數(shù)組。整個數(shù)組將被保存在內(nèi)存中,直到它不再被引用。 有時候可能會因為一個小的內(nèi)存引用導(dǎo)致保存所有的數(shù)據(jù)。

例如, FindDigits 函數(shù)加載整個文件到內(nèi)存,然后搜索第一個連續(xù)的數(shù)字,最后結(jié)果以切片方式返回。

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

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

這段代碼的行為和描述類似,返回的 []byte 指向保存整個文件的數(shù)組。因為切片引用了原始的數(shù)組, 導(dǎo)致 GC 不能釋放數(shù)組的空間;只用到少數(shù)幾個字節(jié)卻導(dǎo)致整個文件的內(nèi)容都一直保存在內(nèi)存里。

要修復(fù)整個問題,可以將感興趣的數(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
}

可以使用 append 實現(xiàn)一個更簡潔的版本。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

您可能感興趣的文章:
  • golang 兩個go程輪流打印一個切片的實現(xiàn)
  • golang切片內(nèi)存應(yīng)用技巧詳解
  • golang常用手冊之切片(Slice)原理
  • Golang slice切片操作之切片的追加、刪除、插入等
  • golang的序列化與反序列化的幾種方式
  • golang實現(xiàn)php里的serialize()和unserialize()序列和反序列方法詳解
  • golang切片反序?qū)嵗?/li>

標(biāo)簽:銅川 黃山 湘潭 湖南 衡水 蘭州 仙桃 崇左

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Golang中切片的用法與本質(zhì)詳解》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266