1. 程式人生 > >go——字典

go——字典

Go中字典型別是散列表(hash table)的一個實現,其官方稱謂是map。
散列表是一個實現了關聯陣列的資料結構,關聯陣列是用於表示鍵值對的無序集合的一種抽象資料型別。
Go中稱鍵值對為鍵-元素對,它把字典中的每個鍵都看作與其對應的元素的索引,這樣的索引再同一個字典值中是唯一的。
下面的程式碼聲明瞭一個字典型別的變數:

var ipSwitches = map[string]bool{}

變數ipSwitches的鍵型別為string,元素型別為bool。
map[string]bool{}表示了一個不包含任何元素的字典值。
與切片型別一樣,字典型別是一個引用型別。也正因此,字典型別的零值是nil。
字典值的長度表示了其中的鍵-元素對的數量,其零值的長度總是0.
將其作為語言內建型別,從執行時層面進行優化,可獲得更高效的效能。
作為無序鍵值對集合,字典要求key必須是支援相等運算子(==、!=)的資料型別。

字典是引用型別,使用make函式或初始化表示式來建立。

package main

import "fmt"

func main() {
	m := make(map[string]int) //make函式建立
	m["a"] = 1
	m["b"] = 2

	m2 := map[int]struct { //key的型別int,value的型別struct
		x int
	}{
		1: {x: 100},
		2: {x: 200},
	}
	fmt.Println(m, m2) //map[a:1 b:2] map[1:{100} 2:{200}]
}

 

字典的基本操作:增刪改查

package main

import "fmt"

func main() {
	m := map[string]int{
		"a": 1,
		"b": 2,
	}

	m["a"] = 10    //修改
	m["c"] = 30    //新增
	fmt.Println(m) //map[a:10 b:2 c:30]

	if v, ok := m["d"]; ok { //使用ok-idiom判斷鍵是否存在
		fmt.Println(v)
	}

	delete(m, "b")      //刪除一個鍵值對
	fmt.Println(m)      //map[a:10 b:2 c:30]
	fmt.Println(m["f"]) //0  訪問不存在的鍵值對時返回0值
}

 

對字典進行迭代每次返回的鍵值次序都不相同

package main

import "fmt"

func main() {
	m := make(map[string]int)

	for i := 0; i < 7; i++ {
		m[string('a'+i)] = i
	}

	for i := 0; i < 4; i++ {
		for k, v := range m {
			fmt.Printf("%s:%d  ", k, v)
		}
		fmt.Println()
	}
}

/*
g:6  a:0  b:1  c:2  d:3  e:4  f:5
e:4  f:5  g:6  a:0  b:1  c:2  d:3
g:6  a:0  b:1  c:2  d:3  e:4  f:5
a:0  b:1  c:2  d:3  e:4  f:5  g:6
*/

 

函式len返回當前鍵值對的數量,cap不接受字典型別.
因記憶體訪問安全和雜湊演算法等緣故,字典被設計成"not addressable",
因此不能直接修改value成員(結構或陣列)。

package main

import "fmt"

func main() {
	type user struct {
		name string
		age  int
	}

	m := map[int]user{
		1: {"kebi", 19},
	}
	fmt.Println(len(m)) //1
	fmt.Println(cap(m)) //invalid argument m (type map[int]user) for cap
	m[1].age += 1       // cannot assign to struct field m[1].age in map
}

 

正確的做法是返回整個value,待修改後再設定字典鍵值,或直接用指標型別。

package main

import "fmt"

func main() {
	type user struct {
		name string
		age  int
	}

	m := map[int]user{
		1: {"kebi", 19},
	}

	u := m[1]  //取出整個value值
	u.age += 1 //現在操作的物件是結構體
	m[1] = u   //重新賦值
	fmt.Println(m)

	m2 := map[int]*user{ //value是指標型別
		1: &user{"Jack", 20},
	}
	m2[1].age++ //m2[1]返回的是指標,透過指標修改目標物件
	fmt.Println(m2[1])
}

 

不能對nil字典進行賦值操作,但卻能讀。

package main

import "fmt"

func main() {
	var m map[string]int //只進行宣告的字典是nil字典
	fmt.Println(m["a"])  //0
	// m["a"] = 1          //panic: assignment to entry in nil map

	m2 := map[string]int{}
	fmt.Println(m == nil, m2 == nil) //true  false
}

 

在迭代期間刪除或新增鍵值是安全的。

package main

import "fmt"

func main() {
	m := make(map[int]int)

	for i := 0; i < 10; i++ {
		m[i] = i + 10
	}

	for k := range m {
		if k == 5 {
			m[100] = 1000
		}
		delete(m, k)
		fmt.Println(k, m)
	}
}

/*
0 map[9:19 2:12 6:16 8:18 7:17 1:11 3:13 4:14 5:15]
2 map[5:15 7:17 1:11 3:13 4:14 8:18 9:19 6:16]
6 map[4:14 5:15 7:17 1:11 3:13 8:18 9:19]
8 map[7:17 1:11 3:13 4:14 5:15 9:19]
9 map[4:14 5:15 7:17 1:11 3:13]
1 map[3:13 4:14 5:15 7:17]
3 map[4:14 5:15 7:17]
4 map[5:15 7:17]
5 map[7:17 100:1000]
7 map[100:1000]
*/

 

執行時會對字典併發操作做出檢測.如果某個任務正在對字典進行寫操作,
那麼其它任務就不能對該字典執行併發操作(讀寫刪除),否則會導致程序崩潰.

package main

import "time"

func main() {
	m := make(map[string]int)

	go func() {
		for {
			m["a"] += 1
			time.Sleep(time.Microsecond)
		}
	}()

	go func() {
		for {
			_ = m["b"]
			time.Sleep(time.Microsecond)
		}
	}()
	select {}
}


//fatal error: concurrent map read and map write