Go基礎編程:Map

Map

《Go語言聖經》:哈希表是一種巧妙而且實用的數據結構。它是一個無序key/value對的集合,其中全部的key都是不一樣的,而後經過給定的key能夠在常數時間複雜度內檢索、更新或刪除對應的value。算法

在Go語言中,一個map就是一個哈希表的引用,map類型能夠寫爲map[K]V,其中K和V分別對應key和value。map中全部的key都有相同的類型,因此的value也有着相同的類型,可是key和value之間能夠是不一樣的數據型。其中K對應的key必須是支持==比較運算符的數據類型,因此map能夠經過測試key是否相等來判斷是否已經存在。雖然浮點數類型也是支持相等運算符比較的,可是將浮點數用作key類型則是一個壞的想法,最壞的狀況是可能出現的NaN和任何浮點數都不相等。對於V對應的value數據類型則沒有任何的限制。數組

聲明安全

map是引用類型,能夠用以下聲明:數據結構

var type_name map[keytype]valuetype

在向map存儲數據前必須初始化併發

var a map[string]string
a["name"]="值" //panic: assignment to entry in nil map

使用內置函數make()初始化函數

var a map[string]string
a = make(map[string]string)
a["name"] = "值"
fmt.Println(a) //map[name:值]

能夠聲明和初始化一塊兒高併發

var a = make(map[string]string)
a["name"] = "值"
fmt.Println(a) //map[name:值]

聲明並初始化帶上數據測試

var a map[string]string = map[string]string{"id": "110"}
a["name"] = "值"
fmt.Println(a) //map[id:110 name:值]

基本操做spa

添加數據,只要初始化後,能夠隨便添加:指針

var a = make(map[string]string)
a["id"]= "001"
a["name"]="hello"
a["age"]="23"
fmt.Println(a) //map[age:23 id:001 name:hello]

修改數據

var a = make(map[string]string)
a["name"] = "hello"
fmt.Println(a) //map[name:hello]
a["name"] = "worrld"
fmt.Println(a) //map[name:worrld]

獲取數據,就算沒有對應的key也不會報錯,這個操做是安全的,會返回對應value的空值

var a = make(map[string]string)
a["name"] = "hello"
fmt.Println(a["name"]) //hello
fmt.Println(a["id"])   //此處無數據,無報錯

刪除數據,使用內置函數delete(),刪除無此key的元素也不報錯,這個操做是安全的

var a = make(map[string]string)
a["id"] = "001"
a["name"] = "hello"
fmt.Println(a) //map[id:001 name:hello]
delete(a, "id")//刪除key爲id的元素
fmt.Println(a)   //map[name:hello]
delete(a, "age") //刪除key爲age 的元素,無報錯
fmt.Println(a)   //map[name:hello]

返回爲空值不表明key不存在,由於這個key的value可能就是空值,須要用下面方法

第一個返回參數是此key對應的value,第二個參數是是否存在的bool值,存在即爲true

if value, ok := a["name"]; ok {
    fmt.Println("key存在,value爲:", value)
}

遍歷,用for range變量,語法和遍歷切片同樣 , map是無序的,打印順序每次不必定同樣

var a = make(map[string]string)
a["id"] = "001"
a["name"] = "hello"
a["lan"] = "go"
for k, v := range a {
    fmt.Printf("%s=>%sn", k, v)
}

map中的元素不是有個變量,不能取地址操做,禁止對map元素取址的緣由是map可能隨着元素數量的增加而從新分配更大的內存空間,從而可能致使以前的地址無效。

fmt.Printf("%p", &a["id"]) //cannot take the address of a["id"]

map的零值爲nil,也就是沒有引用任何哈希表。

var ages map[string]int
fmt.Println(ages == nil) // "true"
fmt.Println(len(ages) == 0) // "true"

和slice同樣,map之間也是不能進行相等比較,惟一的例外是和nil進行比較。要判斷兩個map是否包含相同的key和value。

高併發下讀寫是不安全的

package main
​
import (
    "time"
)
​
func main() {
​
     var a = make(map[string]int)
    ​
     for i := 0; i < 100; i++ {
         go func() {
            a["id"] = 1 //fatal error: concurrent map writes
         }()
     }
    ​
     time.Sleep(1 * time.Second)
​
}
​

高併發下讀寫須要加鎖

package main
​
import (
     "fmt"
     "sync"
     "time"
)
​
var wg sync.Mutex
​
func main() {
     var a = make(map[string]int)
    ​
     for i := 0; i < 100; i++ {
         go func() {
             wg.Lock()
             a["id"] = i
             wg.Unlock()
         }()
     }
    ​
     time.Sleep(1 * time.Second)
    ​
     fmt.Println(a) //map[id:100]
}

在高併發下建議使用標準包下的sync.Map

package main
​
import (
     "fmt"
     "sync"
)
​
func main() {
​
     var a sync.Map
    ​
     //添加元素:key,value
     a.Store("name", "hello")
     a.Store("age","22")
     //獲取一個元素key
     if value, ok := a.Load("name"); ok {
         fmt.Println("key存在,value爲:", value)
     }
     //
     //參數是一對key:value,若是該key存在且沒有被標記刪除則返回原先的value(不更新)和true;不存在則store,返回該value 和false
     if actual, loaded := a.LoadOrStore("id", "001"); !loaded {
         fmt.Printf("不存在key爲id,並增長%sn", actual)
     }
     if actual, loaded := a.LoadOrStore("id", "002"); loaded {
         fmt.Printf("key爲id,不改變value:%sn", actual)
     }
    ​
     //遍歷
     a.Range(func(key, value interface{}) bool {
         fmt.Printf("key:%s,value:%sn", key,value)
     return true
     })
     //刪除key
     a.Delete("id")
     if value, ok := a.Load("id"); !ok {
         fmt.Printf("id刪除了,value爲空:%sn",value)
     }

     //獲取並刪除
     //第一個是值,有返回,無是爲nil,第二個是判斷key是否存在,存在爲true
     if value ,loaded :=a.LoadAndDelete("age");loaded{
        fmt.Printf("age刪除了,刪除前value爲:%sn",value)
     }
     if value, ok := a.Load("age"); !ok {
        fmt.Printf("age刪除了,value爲空:%sn",value)
     }
​
}
​

哈希表

Go的Map是哈希表的引用,上面講了map的用法,但什麼是哈希表呢?

哈希表是爲快速獲取數據的,時間複雜度約爲O(1),數組的獲取數據的時間複雜度也爲O(1)。哈希表的底層是由數組組成的,因此怎麼把哈希表的key轉爲數組下標是關鍵。

哈希算法f(key)能夠用於將任意大小的數據映射到固定大小值的函數,常見的包括MD五、SHA系列等

哈希表key->value 大體流程是下面:

key  ->   f(key)   ->  數組下標  -> value

數組下標能夠經過把key經過哈希算法獲取固定大小值而後對一個數取模(常見的數是數組長度和質數),下面是對數組長度取模

爲了方便,用一些簡單的數字代替哈希好的key

假設有個長度爲7的數組,哈希好的key有 2,4,6,12,故他們分別取模爲:2,4,6,5,故數組存儲以下:

image.png

假設再加一個9,取模爲2,如上2的位置被佔了,這種狀況叫哈希衝突

爲了解決哈希衝突有兩種方案:

  1. 鏈地址法
  2. 開放尋址法

    • 線性探測法
    • 二次探查
    • 雙重散列

鏈地址法:就是在被佔位置加個next指針指向下一個數據,以下:
image.png

相關文章
相關標籤/搜索