Golang高效實踐之array、slice、map實踐


前言

Golang的slice類型為連續同類型數據提供了一個方便並且高效的實現方式。slice的實現是基於array,slice和map一樣是類似於指針語義,傳遞slice和map並不涉及底層數據結構的拷貝,相當於傳遞底層數據結構的指針。

 

Arrays數組 

數組類型的定義需要指定長度和元素的類型。例如,[4]int表示一個四個整數的數組。數組的大小是固定的,數組的大小是類型的一部分,也就是說[4]int 和 [5]int是不同的類型,不能比較。數組可以按序號索引訪問,從0開始,s[n]表示訪問第n個元素。

var a [4]int

a[0] = 1

i := a[0]

// i == 1

數組不需要明確的初始化,默認是數組元素類型的零值: 

// a[2] == 0

[4]int的內存分布為:

Go的數組是值語義,即數組變量代表整個數組,並不是一個指向數組第一個元素的指針(C語言)。這意味着當賦值或者傳遞數組時將會產生數組內容拷貝。

數組指定元素初始化:

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

或者:

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

上面兩種寫法b的類型都是[2]string

package main

import
"fmt"

func main() { b := [...]string{"Penn", "Teller"} fmt.Printf("%T\n", b) }

 

程序輸出:

[2]string

 

Slices切片

由於數組的大小是固定的,不是很靈活,所以在Go的代碼里面不會經常出現。但是,slice是隨處可見的。slice是數組的基礎進行了封裝,更加強大和方便。 

切片的定義為:[]T,其中T是切片元素的類型。不像數組,切片類型不用指定長度。例如:

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

letters的類型為[]string,而不是[4]string

切片可以用內建函數make創建,make的簽名為:

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

其中cap可選

var s []byte

s = make([]byte, 5, 5)

// s == []byte{0, 0, 0, 0, 0}

不指定cap:

s := make([]byte, 5)

切片的零值是nil。對零值調用len和cap函數將會返回0.

切片可以從一個已經存在的切片或者數組中切分生成,切分遵循的是半開閉原則,例如b[1:4]表達式創建一個切片包含b的序號1到3的元素,由此得到新的切片序號將會是從0到2。 

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

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

用於表示切分的開始和結束的序號都是可選的,默認分別是0和切片的長度。例如:

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

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

// b[:] == b

從數組中切分為切片:

package main

 

import "fmt"

 

func main() {

 x := [3]string{"","","Gopher"}

 s := x[:]

 fmt.Println(s)

 fmt.Printf("x type:%T\ns type:%T\n", x, s)

}

程序輸出:

[我 是 Gopher]

x type:[3]string

s type:[]string

切片內部實現:

切片是一個數組段的描述,由一個指向數組的指針,數據段的長度(length),和它的最大能容納數據的大小(capacity):

上面提到的s,一開始的時候由make([]byte, 5)創建時,結構如下:

 

s=s[2:4]相關的結構體:

 

切分不會拷貝切片的數組,將會創建一個新的切片值指向原始的數組。這使得切片的操作像數組索引訪問一樣高效。因此,更改重新切分的切片元素也會影響到原始的切片:

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

如果是想讓重新切分的切片擁有獨立的內存數據,可以使用copy函數:

func copy(dst, src []T) int

例如:

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

copy(t, s)

s = t 

Map哈希表/字典

計算機科學中哈希表是一個很重要的數據結構,Go提供了內建的map類型用於實現哈希表。

Go map類型長這樣:

map[KeyType]ValueType

其中KeyType需要是可比較類型,可比較類型:

可比較類型是值可以用==和!=操作比較的類型,有:

Boolean 值是可以比較的。兩個boolean值如果都是true或者都是false,那么它們就是相等的。

Interger,Float 值是可以比較的。

Complex 值是可以比較的。如果real(u) == real(v) 並且 imag(u) == imag(v),那么兩個complex值相等。

String 值是可以比較的。

Pointer值是可以比較的。如果兩個指針值指向同一個變量那么這兩個指針值相等,或者都是nil。

Channel 值是可以比較的。

Interface值是可以比較的。如果兩個interface值得concrete type和value都相等(前提是concrete type是可比較的),那么這兩個interface相等。如果兩個interface都是nil那么也相等。

var a1 int = 3

var a2 int = 3

var ia1 interface{}

var ia2 interface{}

ia1 = a1

ia2 = a2

if ia1 == ia2 {

 fmt.Println("equal")

}

程序輸出:

equal

Struct值是可比較的,前提是每個字段都是可比較的。如果兩個struct的每個字段都相等,那么這兩個struct相等。

type ST struct {

 name string

 age int

}

s1 := ST{"tom", 19}

s2 := ST{"tom", 19}

fmt.Println(s1 == s2)

程序輸出:

true

 

數組值是可以比較的,前提是數組元素類型是可以比較的。當兩個數據的每個元素都對應相等,那么這兩個數組是相等的。

a1 := [2]string{"iam", "handsome"}

a2 := [2]string{"iam", "handsome"}

fmt.Println(a1 == a2)

程序輸出:

true

需要特別說明的是如果兩個interface指向的實際類型(concrete type)是不可比較類型,如果比較這兩個interface將會觸發運行時panic,例如:

a1 := []int{1,3}

a2 := []int{1,3}

var ia1 interface{}

var ia2 interface{}

ia1 = a1

ia2 = a2

if ia1 == ia2 {

 fmt.Println("equal")

} 

程序運行結果:

panic: runtime error: comparing uncomparable type []int

 

goroutine 1 [running]:

main.main()

 /Users/haiweilu/saas/src/awesomeProject/channel/main.go:24 +0xb2

Slice,map和function 值是不能比較的。但是有一個特例,就是可以和nil比較,判斷slice,map和function是否是nil。 

所以slice,map和funciton值不能作為map的key。map的ValueType可以是任意類型,當然也包括map類型。例如:

var m map[string]int

Map類型是引用類型,類似於指針和切片,所以上述m的值是nil,它指向一個還沒初始化的map,即map的零值是nil。對nil map值進行讀寫訪問會觸發運行時panic。為了避免這種情況,可以用內建函數make創建map:

m = make(map[string]int)

Make函數分配並初始化一個哈希表數據結構,並且返回一個指向這個結構的map值。

 

Map的使用

設置一個key為”route”,value為66的元素:

m["route"] = 66

根據key索引訪問value:

i := m["route"]

如果key對應的value不存在,將會返回該value類型對應的零值,例如:

j := m["root”],j的值是0

求map的元素數量:

n := len(m)

根據key刪除map對應的k-v:

delete(m, "route")

可以用”common,ok”表達式判斷map的key是否存在:

_, ok := m["route"]

如果”route”存在,ok為true,否則為false

遍歷map:

for key, value := range m {

    fmt.Println("Key:", key, "Value:", value)

}

初始化map的另外一種方法:

commits := map[string]int{

    "rsc": 3711,

    "r":   2138,

    "gri": 1908,

    "adg": 912,

}

m = map[string]int{} 

用map實現set

由於map索引對應key不存在時返回value類型的零值,所以我們可以用map[KeyType]bool來實現一個set 

struct作為map的key實現多維索引

例如:

type Key struct {

    Path, Country string

}

hits := make(map[Key]int)

 

hits[Key{"/", "vn"}]++
也可以這樣:

n := hits[Key{"/ref/spec", "ch"}]

Map的並發

Map的操作不是原子操作,所以多個goroutine並發讀寫map會導致運行時panic。同時讀沒有問題。可以通過讀寫鎖的方式實現同步並發讀寫:

var counter = struct{

    sync.RWMutex

    m map[string]int

}{m: make(map[string]int)}

 

讀:

counter.RLock()

n := counter.m["some_key"]

counter.RUnlock()

fmt.Println("some_key:", n)

 

寫:

counter.Lock()

counter.m["some_key"]++

counter.Unlock()

有序map

Map中的key不保證順序,也就說保證每次遍歷同一個map的key返回順序都是一致的,如果需要key是有序的可以通過增加一個輔助的切片來實現:

import "sort"

 

var m map[int]string

var keys []int

for k := range m {

    keys = append(keys, k)

}

sort.Ints(keys)

for _, k := range keys {

    fmt.Println("Key:", k, "Value:", m[k])

}

總結

文檔介紹了array、slice和map的各種使用場景,希望能夠幫助大家少點踩坑。 

參考

https://blog.golang.org/go-slices-usage-and-internals

https://blog.golang.org/go-maps-in-action
https://golang.org/ref/spec#Comparison_operators 

 

我的博客即將同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=thg523juerih

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM