主頁(yè) > 知識(shí)庫(kù) > Go語(yǔ)言中的Array、Slice、Map和Set使用詳解

Go語(yǔ)言中的Array、Slice、Map和Set使用詳解

熱門標(biāo)簽:海外照相館地圖標(biāo)注入駐 外呼系統(tǒng)多少錢一年 智能營(yíng)銷軟件 工商信用卡外呼系統(tǒng)教程 外呼系統(tǒng)如何接收服務(wù)密碼 經(jīng)常接到推銷電話機(jī)器人的電話 滁州自建外呼系統(tǒng) 客服級(jí)電銷機(jī)器人 旅游廁所如何電子地圖標(biāo)注

Array(數(shù)組)

內(nèi)部機(jī)制

在 Go 語(yǔ)言中數(shù)組是固定長(zhǎng)度的數(shù)據(jù)類型,它包含相同類型的連續(xù)的元素,這些元素可以是內(nèi)建類型,像數(shù)字和字符串,也可以是結(jié)構(gòu)類型,元素可以通過(guò)唯一的索引值訪問(wèn),從 0 開始。

數(shù)組是很有價(jià)值的數(shù)據(jù)結(jié)構(gòu),因?yàn)樗膬?nèi)存分配是連續(xù)的,內(nèi)存連續(xù)意味著可是讓它在 CPU 緩存中待更久,所以迭代數(shù)組和移動(dòng)元素都會(huì)非常迅速。

數(shù)組聲明和初始化

通過(guò)指定數(shù)據(jù)類型和元素個(gè)數(shù)(數(shù)組長(zhǎng)度)來(lái)聲明數(shù)組。

復(fù)制代碼 代碼如下:

// 聲明一個(gè)長(zhǎng)度為5的整數(shù)數(shù)組
var array [5]int

一旦數(shù)組被聲明了,那么它的數(shù)據(jù)類型跟長(zhǎng)度都不能再被改變。如果你需要更多的元素,那么只能創(chuàng)建一個(gè)你想要長(zhǎng)度的新的數(shù)組,然后把原有數(shù)組的元素拷貝過(guò)去。

Go 語(yǔ)言中任何變量被聲明時(shí),都會(huì)被默認(rèn)初始化為各自類型對(duì)應(yīng)的 0 值,數(shù)組當(dāng)然也不例外。當(dāng)一個(gè)數(shù)組被聲明時(shí),它里面包含的每個(gè)元素都會(huì)被初始化為 0 值。

一種快速創(chuàng)建和初始化數(shù)組的方法是使用數(shù)組字面值。數(shù)組字面值允許我們聲明我們需要的元素個(gè)數(shù)并指定數(shù)據(jù)類型:

復(fù)制代碼 代碼如下:

// 聲明一個(gè)長(zhǎng)度為5的整數(shù)數(shù)組
// 初始化每個(gè)元素
array := [5]int{7, 77, 777, 7777, 77777}

如果你把長(zhǎng)度寫成 ...,Go 編譯器將會(huì)根據(jù)你的元素來(lái)推導(dǎo)出長(zhǎng)度:
復(fù)制代碼 代碼如下:

// 通過(guò)初始化值的個(gè)數(shù)來(lái)推導(dǎo)出數(shù)組容量
array := [...]int{7, 77, 777, 7777, 77777}

如果我們知道想要數(shù)組的長(zhǎng)度,但是希望對(duì)指定位置元素初始化,可以這樣:

復(fù)制代碼 代碼如下:

// 聲明一個(gè)長(zhǎng)度為5的整數(shù)數(shù)組
// 為索引為1和2的位置指定元素初始化
// 剩余元素為0值
array := [5]int{1: 77, 2: 777}

使用數(shù)組

使用 [] 操作符來(lái)訪問(wèn)數(shù)組元素:

復(fù)制代碼 代碼如下:

array := [5]int{7, 77, 777, 7777, 77777}
// 改變索引為2的元素的值
array[2] = 1

我們可以定義一個(gè)指針數(shù)組:

復(fù)制代碼 代碼如下:

array := [5]*int{0: new(int), 1: new(int)}

// 為索引為0和1的元素賦值
*array[0] = 7
*array[1] = 77


在 Go 語(yǔ)言中數(shù)組是一個(gè)值,所以可以用它來(lái)進(jìn)行賦值操作。一個(gè)數(shù)組可以被賦值給任意相同類型的數(shù)組:

復(fù)制代碼 代碼如下:

var array1 [5]string
array2 := [5]string{"Red", "Blue", "Green", "Yellow", "Pink"}
array1 = array2

注意數(shù)組的類型同時(shí)包括數(shù)組的長(zhǎng)度和可以被存儲(chǔ)的元素類型,數(shù)組類型完全相同才可以互相賦值,比如下面這樣就不可以:
復(fù)制代碼 代碼如下:

var array1 [4]string
array2 := [5]string{"Red", "Blue", "Green", "Yellow", "Pink"}
array1 = array2

// 編譯器會(huì)報(bào)錯(cuò)
Compiler Error:
cannot use array2 (type [5]string) as type [4]string in assignment


拷貝一個(gè)指針數(shù)組實(shí)際上是拷貝指針值,而不是指針指向的值:
復(fù)制代碼 代碼如下:

var array1 [3]*string
array2 := [3]*string{new(string), new(string), new(string)}
*array2[0] = "Red"
*array2[1] = "Blue"
*array2[2] = "Green"

array1 = array2
// 賦值完成后,兩組指針數(shù)組指向同一字符串

多維數(shù)組

數(shù)組總是一維的,但是可以組合成多維的。多維數(shù)組通常用于有父子關(guān)系的數(shù)據(jù)或者是坐標(biāo)系數(shù)據(jù):

復(fù)制代碼 代碼如下:

// 聲明一個(gè)二維數(shù)組
var array [4][2]int

// 使用數(shù)組字面值聲明并初始化
array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}

// 指定外部數(shù)組索引位置初始化
array := [4][2]int{1: {20, 21}, 3: {40, 41}}

// 同時(shí)指定內(nèi)外部數(shù)組索引位置初始化
array := [4][2]int{1: {0: 20}, 3: {1: 41}}

同樣通過(guò) [] 操作符來(lái)訪問(wèn)數(shù)組元素:

復(fù)制代碼 代碼如下:

var array [2][2]int

array[0][0] = 0
array[0][1] = 1
array[1][0] = 2
array[1][1] = 3

也同樣的相同類型的多維數(shù)組可以相互賦值:

復(fù)制代碼 代碼如下:

var array1 = [2][2]int
var array2 = [2][2]int

array[0][0] = 0
array[0][1] = 1
array[1][0] = 2
array[1][1] = 3

array1 = array2

因?yàn)閿?shù)組是值,我們可以拷貝單獨(dú)的維:

復(fù)制代碼 代碼如下:

var array3 [2]int = array1[1]
var value int = array1[1][0]

在函數(shù)中傳遞數(shù)組

在函數(shù)中傳遞數(shù)組是非常昂貴的行為,因?yàn)樵诤瘮?shù)之間傳遞變量永遠(yuǎn)是傳遞值,所以如果變量是數(shù)組,那么意味著傳遞整個(gè)數(shù)組,即使它很大很大很大。。。

舉個(gè)栗子,創(chuàng)建一個(gè)有百萬(wàn)元素的整形數(shù)組,在64位的機(jī)器上它需要8兆的內(nèi)存空間,來(lái)看看我們聲明它和傳遞它時(shí)發(fā)生了什么:

復(fù)制代碼 代碼如下:

var array [1e6]int
foo(array)
func foo(array [1e6]int) {
  ...
}

每一次 foo 被調(diào)用,8兆內(nèi)存將會(huì)被分配在棧上。一旦函數(shù)返回,會(huì)彈棧并釋放內(nèi)存,每次都需要8兆空間。

Go 語(yǔ)言當(dāng)然不會(huì)這么傻,有更好的方法來(lái)在函數(shù)中傳遞數(shù)組,那就是傳遞指向數(shù)組的指針,這樣每次只需要分配8字節(jié)內(nèi)存:

復(fù)制代碼 代碼如下:

var array [1e6]int
foo(array)
func foo(array *[1e6]int){
  ...
}

但是注意如果你在函數(shù)中改變指針指向的值,那么原始數(shù)組的值也會(huì)被改變。幸運(yùn)的是 slice(切片)可以幫我們處理好這些問(wèn)題,來(lái)一起看看。

Slice(切片)

內(nèi)部機(jī)制和基礎(chǔ)

slice 是一種可以動(dòng)態(tài)數(shù)組,可以按我們的希望增長(zhǎng)和收縮。它的增長(zhǎng)操作很容易使用,因?yàn)橛袃?nèi)建的 append 方法。我們也可以通過(guò) relice 操作化簡(jiǎn) slice。因?yàn)?slice 的底層內(nèi)存是連續(xù)分配的,所以 slice 的索引,迭代和垃圾回收性能都很好。

slice 是對(duì)底層數(shù)組的抽象和控制。它包含 Go 需要對(duì)底層數(shù)組管理的三種元數(shù)據(jù),分別是:

1.指向底層數(shù)組的指針
2.slice 中元素的長(zhǎng)度
3.slice 的容量(可供增長(zhǎng)的最大值)

創(chuàng)建和初始化

Go 中創(chuàng)建 slice 有很多種方法,我們一個(gè)一個(gè)來(lái)看。

第一個(gè)方法是使用內(nèi)建的函數(shù) make。當(dāng)我們使用 make 創(chuàng)建時(shí),一個(gè)選項(xiàng)是可以指定 slice 的長(zhǎng)度:

復(fù)制代碼 代碼如下:

slice := make([]string, 5)

如果只指定了長(zhǎng)度,那么容量默認(rèn)等于長(zhǎng)度。我們可以分別指定長(zhǎng)度和容量:

復(fù)制代碼 代碼如下:

slice := make([]int, 3, 5)

當(dāng)我們分別指定了長(zhǎng)度和容量,我們創(chuàng)建的 slice 就可以擁有一開始并沒(méi)有訪問(wèn)的底層數(shù)組的容量。上面代碼的 slice 中,可以訪問(wèn)3個(gè)元素,但是底層數(shù)組有5個(gè)元素。兩個(gè)與長(zhǎng)度不相干的元素可以被 slice 來(lái)用。新創(chuàng)建的 slice 同樣可以共享底層數(shù)組和已存在的容量。

不允許創(chuàng)建長(zhǎng)度大于容量的 slice:

復(fù)制代碼 代碼如下:

slice := make([]int, 5, 3)

Compiler Error:
len larger than cap in make([]int)

慣用的創(chuàng)建 slice 的方法是使用 slice 字面量。跟創(chuàng)建數(shù)組很類似,不過(guò)不用指定 []里的值。初始的長(zhǎng)度和容量依賴于元素的個(gè)數(shù):

復(fù)制代碼 代碼如下:

// 創(chuàng)建一個(gè)字符串 slice
// 長(zhǎng)度和容量都是 5
slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}

在使用 slice 字面量創(chuàng)建 slice 時(shí)有一種方法可以初始化長(zhǎng)度和容量,那就是初始化索引。下面是個(gè)例子:

復(fù)制代碼 代碼如下:

// 創(chuàng)建一個(gè)字符串 slice
// 初始化一個(gè)有100個(gè)元素的空的字符串 slice
slice := []string{99: ""}

nil 和 empty slice

有的時(shí)候我們需要?jiǎng)?chuàng)建一個(gè) nil slice,創(chuàng)建一個(gè) nil slice 的方法是聲明它但不初始化它:

復(fù)制代碼 代碼如下:

var slice []int

創(chuàng)建一個(gè) nil slice 是創(chuàng)建 slice 最基本的方法,很多標(biāo)準(zhǔn)庫(kù)和內(nèi)建函數(shù)都可以使用它。當(dāng)我們想要表示一個(gè)并不存在的 slice 時(shí)它變得非常有用,比如一個(gè)返回 slice 的函數(shù)中發(fā)生異常的時(shí)候。

創(chuàng)建 empty slice 的方法就是聲明并初始化一下:

復(fù)制代碼 代碼如下:

// 使用 make 創(chuàng)建
silce := make([]int, 0)

// 使用 slice 字面值創(chuàng)建
slice := []int{}

empty slice 包含0個(gè)元素并且底層數(shù)組沒(méi)有分配存儲(chǔ)空間。當(dāng)我們想要表示一個(gè)空集合時(shí)它很有用處,比如一個(gè)數(shù)據(jù)庫(kù)查詢返回0個(gè)結(jié)果。

不管我們用 nil slice 還是 empty slice,內(nèi)建函數(shù) append,len和cap的工作方式完全相同。

使用 slice

為一個(gè)指定索引值的 slice 賦值跟之前數(shù)組賦值的做法完全相同。改變單個(gè)元素的值使用 [] 操作符:

復(fù)制代碼 代碼如下:

slice := []int{10, 20, 30, 40, 50}
slice[1] = 25

我們可以在底層數(shù)組上對(duì)一部分?jǐn)?shù)據(jù)進(jìn)行 slice 操作,來(lái)創(chuàng)建一個(gè)新的 slice:

復(fù)制代碼 代碼如下:

// 長(zhǎng)度為5,容量為5
slice := []int{10, 20, 30, 40, 50}

// 長(zhǎng)度為2,容量為4
newSlice := slice[1:3]

在 slice 操作之后我們得到了兩個(gè) slice,它們共享底層數(shù)組。但是它們能訪問(wèn)底層數(shù)組的范圍卻不同,newSlice 不能訪問(wèn)它頭指針前面的值。

計(jì)算任意 new slice 的長(zhǎng)度和容量可以使用下面的公式:

復(fù)制代碼 代碼如下:

對(duì)于 slice[i:j] 和底層容量為 k 的數(shù)組
長(zhǎng)度:j - i
容量:k - i

必須再次明確一下現(xiàn)在是兩個(gè) slice 共享底層數(shù)組,因此只要有一個(gè) slice 改變了底層數(shù)組的值,那么另一個(gè)也會(huì)隨之改變:

復(fù)制代碼 代碼如下:

slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
newSlice[1] = 35

改變 newSlice 的第二個(gè)元素的值,也會(huì)同樣改變 slice 的第三個(gè)元素的值。

一個(gè) slice 只能訪問(wèn)它長(zhǎng)度范圍內(nèi)的索引,試圖訪問(wèn)超出長(zhǎng)度范圍的索引會(huì)產(chǎn)生一個(gè)運(yùn)行時(shí)錯(cuò)誤。容量只可以用來(lái)增長(zhǎng),它只有被合并到長(zhǎng)度才可以被訪問(wèn):

復(fù)制代碼 代碼如下:

slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
newSlice[3] = 45

Runtime Exception:
panic: runtime error: index out of range

容量可以被合并到長(zhǎng)度里,通過(guò)內(nèi)建的 append 函數(shù)。

slice 增長(zhǎng)

slice 比 數(shù)組的優(yōu)勢(shì)就在于它可以按照我們的需要增長(zhǎng),我們只需要使用 append 方法,然后 Go 會(huì)為我們做好一切。

使用 append 方法時(shí)我們需要一個(gè)源 slice 和需要附加到它里面的值。當(dāng) append 方法返回時(shí),它返回一個(gè)新的 slice,append 方法總是增長(zhǎng) slice 的長(zhǎng)度,另一方面,如果源 slice 的容量足夠,那么底層數(shù)組不會(huì)發(fā)生改變,否則會(huì)重新分配內(nèi)存空間。

復(fù)制代碼 代碼如下:

// 創(chuàng)建一個(gè)長(zhǎng)度和容量都為5的 slice
slice := []int{10, 20, 30, 40, 50}

// 創(chuàng)建一個(gè)新的 slice
newSlice := slice[1:3]

// 為新的 slice append 一個(gè)值
newSlice = append(newSlice, 60)

因?yàn)?newSlice 有可用的容量,所以在 append 操作之后 slice 索引為 3 的值也變成了 60,之前說(shuō)過(guò)這是因?yàn)?slice 和 newSlice 共享同樣的底層數(shù)組。

如果沒(méi)有足夠可用的容量,append 函數(shù)會(huì)創(chuàng)建一個(gè)新的底層數(shù)組,拷貝已存在的值和將要被附加的新值:

復(fù)制代碼 代碼如下:

// 創(chuàng)建長(zhǎng)度和容量都為4的 slice
slice := []int{10, 20, 30, 40}

// 附加一個(gè)新值到 slice,因?yàn)槌隽巳萘浚詴?huì)創(chuàng)建新的底層數(shù)組
newSlice := append(slice, 50)

append 函數(shù)重新創(chuàng)建底層數(shù)組時(shí),容量會(huì)是現(xiàn)有元素的兩倍(前提是元素個(gè)數(shù)小于1000),如果元素個(gè)數(shù)超過(guò)1000,那么容量會(huì)以 1.25 倍來(lái)增長(zhǎng)。

slice 的第三個(gè)索引參數(shù)

slice 還可以有第三個(gè)索引參數(shù)來(lái)限定容量,它的目的不是為了增加容量,而是提供了對(duì)底層數(shù)組的一個(gè)保護(hù)機(jī)制,以方便我們更好的控制 append 操作,舉個(gè)栗子:

復(fù)制代碼 代碼如下:

source := []string{"apple", "orange", "plum", "banana", "grape"}

// 接著我們?cè)谠?slice 之上創(chuàng)建一個(gè)新的 slice
slice := source[2:3:4]

新創(chuàng)建的 slice 長(zhǎng)度為 1,容量為 2,可以看出長(zhǎng)度和容量的計(jì)算公式也很簡(jiǎn)單:

復(fù)制代碼 代碼如下:

對(duì)于 slice[i:j:k]  或者 [2:3:4]

長(zhǎng)度: j - i       或者   3 - 2
容量: k - i       或者   4 - 2

如果我們?cè)噲D設(shè)置比可用容量更大的容量,會(huì)得到一個(gè)運(yùn)行時(shí)錯(cuò)誤:

復(fù)制代碼 代碼如下:

slice := source[2:3:6]


Runtime Error:
panic: runtime error: slice bounds out of range

限定容量最大的用處是我們?cè)趧?chuàng)建新的 slice 時(shí)候限定容量與長(zhǎng)度相同,這樣以后再給新的 slice 增加元素時(shí)就會(huì)分配新的底層數(shù)組,而不會(huì)影響原有 slice 的值:

復(fù)制代碼 代碼如下:

source := []string{"apple", "orange", "plum", "banana", "grape"}

// 接著我們?cè)谠?slice 之上創(chuàng)建一個(gè)新的 slice
// 并且設(shè)置長(zhǎng)度和容量相同
slice := source[2:3:3]

// 添加一個(gè)新元素
slice = append(slice, "kiwi")

如果沒(méi)有第三個(gè)索引參數(shù)限定,添加 kiwi 這個(gè)元素時(shí)就會(huì)覆蓋掉 banana。

內(nèi)建函數(shù) append 是一個(gè)變參函數(shù),意思就是你可以一次添加多個(gè)元素,比如:

復(fù)制代碼 代碼如下:

s1 := []int{1, 2}
s2 := []int{3, 4}

fmt.Printf("%v\n", append(s1, s2...))

Output:
[1 2 3 4]

迭代 slice

slice 也是一種集合,所以可以被迭代,用 for 配合 range 來(lái)迭代:

復(fù)制代碼 代碼如下:

slice := []int{10, 20, 30, 40, 50}

for index, value := range slice {
  fmt.Printf("Index: %d  Value: %d\n", index, value)
}

Output:
Index: 0  Value: 10
Index: 1  Value: 20
Index: 2  Value: 30
Index: 3  Value: 40
Index: 4  Value: 50

當(dāng)?shù)鷷r(shí) range 關(guān)鍵字會(huì)返回兩個(gè)值,第一個(gè)是索引值,第二個(gè)是索引位置值的拷貝。注意:返回的是值的拷貝而不是引用,如果我們把值的地址作為指針使用,會(huì)得到一個(gè)錯(cuò)誤,來(lái)看看為啥:

復(fù)制代碼 代碼如下:

slice := []int{10, 20, 30 ,40}

for index, value := range slice {
  fmt.Printf("Value: %d  Value-Addr: %X  ElemAddr: %X\n", value, value, slice[index])
}

Output:
Value: 10  Value-Addr: 10500168  ElemAddr: 1052E100
Value: 20  Value-Addr: 10500168  ElemAddr: 1052E104
Value: 30  Value-Addr: 10500168  ElemAddr: 1052E108
Value: 40  Value-Addr: 10500168  ElemAddr: 1052E10C

value 變量的地址總是相同的因?yàn)樗皇前粋€(gè)拷貝。如果想得到每個(gè)元素的真是地址可以使用 slice[index]。

如果不需要索引值,可以使用 _ 操作符來(lái)忽略它:

復(fù)制代碼 代碼如下:

slice := []int{10, 20, 30, 40}

for _, value := range slice {
  fmt.Printf("Value: %d\n", value)
}


Output:
Value: 10
Value: 20
Value: 30
Value: 40

range 總是從開始一次遍歷,如果你想控制遍歷的step,就用傳統(tǒng)的 for 循環(huán):

復(fù)制代碼 代碼如下:

slice := []int{10, 20, 30, 40}

for index := 2; index lt; len(slice); index++ {
  fmt.Printf("Index: %d  Value: %d\n", index, slice[index])
}


Output:
Index: 2  Value: 30
Index: 3  Value: 40

同數(shù)組一樣,另外兩個(gè)內(nèi)建函數(shù) len 和 cap 分別返回 slice 的長(zhǎng)度和容量。

多維 slice

也是同數(shù)組一樣,slice 可以組合為多維的 slice:

復(fù)制代碼 代碼如下:

slice := [][]int{{10}, {20, 30}}

需要注意的是使用 append 方法時(shí)的行為,比如我們現(xiàn)在對(duì) slice[0] 增加一個(gè)元素:

復(fù)制代碼 代碼如下:

slice := [][]int{{10}, {20, 30}}
slice[0] = append(slice[0], 20)

那么只有 slice[0] 會(huì)重新創(chuàng)建底層數(shù)組,slice[1] 則不會(huì)。

在函數(shù)間傳遞 slice

在函數(shù)間傳遞 slice 是很廉價(jià)的,因?yàn)?slice 相當(dāng)于是指向底層數(shù)組的指針,讓我們創(chuàng)建一個(gè)很大的 slice 然后傳遞給函數(shù)調(diào)用它:

復(fù)制代碼 代碼如下:

slice := make([]int, 1e6)

slice = foo(slice)

func foo(slice []int) []int {
    ...
    return slice
}

Map

內(nèi)部機(jī)制

map 是一種無(wú)序的鍵值對(duì)的集合。map 最重要的一點(diǎn)是通過(guò) key 來(lái)快速檢索數(shù)據(jù),key 類似于索引,指向數(shù)據(jù)的值。

map 是一種集合,所以我們可以像迭代數(shù)組和 slice 那樣迭代它。不過(guò),map 是無(wú)序的,我們無(wú)法決定它的返回順序,這是因?yàn)?map 是使用 hash 表來(lái)實(shí)現(xiàn)的。

map 的 hash 表包含了一個(gè)桶集合(collection of buckets)。當(dāng)我們存儲(chǔ),移除或者查找鍵值對(duì)(key/value pair)時(shí),都會(huì)從選擇一個(gè)桶開始。在映射(map)操作過(guò)程中,我們會(huì)把指定的鍵值(key)傳遞給 hash 函數(shù)(又稱散列函數(shù))。hash 函數(shù)的作用是生成索引,索引均勻的分布在所有可用的桶上。hash 表算法詳見:July的博客—從頭到尾徹底解析 hash 表算法

創(chuàng)建和初始化

Go 語(yǔ)言中有多種方法創(chuàng)建和初始化 map。我們可以使用內(nèi)建函數(shù) make 也可以使用 map 字面值:

復(fù)制代碼 代碼如下:

// 通過(guò) make 來(lái)創(chuàng)建
dict := make(map[string]int)

// 通過(guò)字面值創(chuàng)建
dict := map[string]string{"Red": "#da1337", "Orange": "#e95a22"}

使用字面值是創(chuàng)建 map 慣用的方法(為什么不使用make)。初始化 map 的長(zhǎng)度依賴于鍵值對(duì)的數(shù)量。

map 的鍵可以是任意內(nèi)建類型或者是 struct 類型,map 的值可以是使用 ==操作符的表達(dá)式。slice,function 和 包含 slice 的 struct 類型不可以作為 map 的鍵,否則會(huì)編譯錯(cuò)誤:

復(fù)制代碼 代碼如下:

dict := map[[]string]int{}

Compiler Exception:
invalid map key type []string

使用 map

給 map 賦值就是指定合法類型的鍵,然后把值賦給鍵:

復(fù)制代碼 代碼如下:

colors := map[string]string{}
colors["Red"] = "#da1337"

如果不初始化 map,那么就會(huì)創(chuàng)建一個(gè) nil map。nil map 不能用來(lái)存放鍵值對(duì),否則會(huì)報(bào)運(yùn)行時(shí)錯(cuò)誤:

復(fù)制代碼 代碼如下:

var colors map[string]string
colors["Red"] = "#da1337"

Runtime Error:
panic: runtime error: assignment to entry in nil map

測(cè)試 map 的鍵是否存在是 map 操作的重要部分,因?yàn)樗梢宰屛覀兣袛嗍欠窨梢詧?zhí)行一個(gè)操作,或者是往 map 里緩存一個(gè)值。它也可以被用來(lái)比較兩個(gè) map 的鍵值對(duì)是否匹配或者缺失。

從 map 里檢索一個(gè)值有兩種選擇,我們可以同時(shí)檢索值并且判斷鍵是否存在:

復(fù)制代碼 代碼如下:

value, exists := colors["Blue"]
if exists {
  fmt.Println(value)
}

另一種選擇是只返回值,然后判斷是否是零值來(lái)確定鍵是否存在。但是只有你確定零值是非法值的時(shí)候這招才管用:

復(fù)制代碼 代碼如下:

value := colors["Blue"]
if value != "" {
  fmt.Println(value)
}

當(dāng)索引一個(gè) map 取值時(shí)它總是會(huì)返回一個(gè)值,即使鍵不存在。上面的例子就返回了對(duì)應(yīng)類型的零值。

迭代一個(gè) map 和迭代數(shù)組和 slice 是一樣的,使用 range 關(guān)鍵字,不過(guò)在迭代 map 時(shí)我們不使用 index/value 而使用 key/value 結(jié)構(gòu):

復(fù)制代碼 代碼如下:

colors := map[string]string{
    "AliceBlue":   "#f0f8ff",
    "Coral":       "#ff7F50",
    "DarkGray":    "#a9a9a9",
    "ForestGreen": "#228b22",
}

for key, value := range colors {
  fmt.Printf("Key: %s  Value: %s\n", key, value)
}

如果我們想要從 map 中移除一個(gè)鍵值對(duì),使用內(nèi)建函數(shù) delete(要是也能返回移除是否成功就好了,哎。。。):

復(fù)制代碼 代碼如下:

delete(colors, "Coral")

for key, value := range colors {
  fmt.Println("Key: %s  Value: %s\n", key, value)
}

在函數(shù)間傳遞 map

在函數(shù)間傳遞 map 不是傳遞 map 的拷貝。所以如果我們?cè)诤瘮?shù)中改變了 map,那么所有引用 map 的地方都會(huì)改變:

復(fù)制代碼 代碼如下:

func main() {
  colors := map[string]string{
     "AliceBlue":   "#f0f8ff",
     "Coral":       "#ff7F50",
     "DarkGray":    "#a9a9a9",
     "ForestGreen": "#228b22",
  }

  for key, value := range colors {
      fmt.Printf("Key: %s  Value: %s\n", key, value)
  }

  removeColor(colors, "Coral")

  for key, value := range colors {
      fmt.Printf("Key: %s  Value: %s\n", key, value)
  }
}

func removeColor(colors map[string]string, key string) {
    delete(colors, key)
}

執(zhí)行會(huì)得到以下結(jié)果:

復(fù)制代碼 代碼如下:

Key: AliceBlue Value: #F0F8FF
Key: Coral Value: #FF7F50
Key: DarkGray Value: #A9A9A9
Key: ForestGreen Value: #228B22
   
Key: AliceBlue Value: #F0F8FF
Key: DarkGray Value: #A9A9A9
Key: ForestGreen Value: #228B22

可以看出來(lái)傳遞 map 也是十分廉價(jià)的,類似 slice。

Set

Go 語(yǔ)言本身是不提供 set 的,但是我們可以自己實(shí)現(xiàn)它,下面就來(lái)試試:

復(fù)制代碼 代碼如下:

package main

import(
  "fmt"
  "sync"
)

type Set struct {
  m map[int]bool
  sync.RWMutex
}

func New() *Set {
  return Set{
    m: map[int]bool{},
  }
}

func (s *Set) Add(item int) {
  s.Lock()
  defer s.Unlock()
  s.m[item] = true
}

func (s *Set) Remove(item int) {
  s.Lock()
  s.Unlock()
  delete(s.m, item)
}

func (s *Set) Has(item int) bool {
  s.RLock()
  defer s.RUnlock()
  _, ok := s.m[item]
  return ok
}

func (s *Set) Len() int {
  return len(s.List())
}

func (s *Set) Clear() {
  s.Lock
  defer s.Unlock()
  s.m = map[int]bool{}
}

func (s *Set) IsEmpty() bool {
  if s.Len() == 0 {
    return true
  }
  return false
}

func (s *Set) List() []int {
  s.RLock()
  defer s.RUnlock()
  list := []int{}
  for item := range s.m {
    list = append(list, item)
  }
  return list
}

func main() {
  // 初始化
  s := New()
 
  s.Add(1)
  s.Add(1)
  s.Add(2)

  s.Clear()
  if s.IsEmpty() {
    fmt.Println("0 item")
  }
 
  s.Add(1)
  s.Add(2)
  s.Add(3)
 
  if s.Has(2) {
    fmt.Println("2 does exist")
  }
 
  s.Remove(2)
  s.Remove(3)
  fmt.Println("list of all items", S.List())
}

注意我們只是使用了 int 作為鍵,你可以自己實(shí)現(xiàn)用 interface{} 作為鍵,做成更通用的 Set,另外,這個(gè)實(shí)現(xiàn)是線程安全的。

總結(jié)

1.數(shù)組是 slice 和 map 的底層結(jié)構(gòu)。
2.slice 是 Go 里面慣用的集合數(shù)據(jù)的方法,map 則是用來(lái)存儲(chǔ)鍵值對(duì)。
3.內(nèi)建函數(shù) make 用來(lái)創(chuàng)建 slice 和 map,并且為它們指定長(zhǎng)度和容量等等。slice 和 map 字面值也可以做同樣的事。
4.slice 有容量的約束,不過(guò)可以通過(guò)內(nèi)建函數(shù) append 來(lái)增加元素。
5.map 沒(méi)有容量一說(shuō),所以也沒(méi)有任何增長(zhǎng)限制。
6.內(nèi)建函數(shù) len 可以用來(lái)獲得 slice 和 map 的長(zhǎng)度。
7.內(nèi)建函數(shù) cap 只能作用在 slice 上。
8.可以通過(guò)組合方式來(lái)創(chuàng)建多維數(shù)組和 slice。map 的值可以是 slice 或者另一個(gè) map。slice 不能作為 map 的鍵。
9.在函數(shù)之間傳遞 slice 和 map 是相當(dāng)廉價(jià)的,因?yàn)樗麄儾粫?huì)傳遞底層數(shù)組的拷貝。

您可能感興趣的文章:
  • golang針對(duì)map的判斷,刪除操作示例
  • go 判斷兩個(gè) slice/struct/map 是否相等的實(shí)例
  • Golang 使用map需要注意的幾個(gè)點(diǎn)
  • golang判斷key是否在map中的代碼

標(biāo)簽:晉城 深圳 九江 運(yùn)城 喀什 湘潭 本溪 楚雄

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Go語(yǔ)言中的Array、Slice、Map和Set使用詳解》,本文關(guān)鍵詞  語(yǔ)言,中的,Array,Slice,Map,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《Go語(yǔ)言中的Array、Slice、Map和Set使用詳解》相關(guān)的同類信息!
  • 本頁(yè)收集關(guān)于Go語(yǔ)言中的Array、Slice、Map和Set使用詳解的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章