一 集合map
1.1 map的创建
Go内置了map类型,map是一个无序键值对集合(也有一些书籍翻译为字典)。
普通创建:
// 声明一个map类型,[]内的类型指任意可以进行比较的类型 int指值类型
m := map[string]int{"a":1,"b":2}
fmt.Print(m["a"])
make方式创建map:
type Person struct{
    ID string
    Name string
}
func main() {
    var m map[string] Person                
    m = make(map[string] Person)
    m["123"] = Person{"123","Tom"}
    p,isFind := m["123"]
    fmt.Println(isFind)        //true
    fmt.Println(p)            //{123 Tom}
}
注意:golang中map的 key 通常 key 为 int 、string,但也可以是其他类型如:bool、数字、string、指针、channel,还可以是只包含前面几个类型的接口、结构体、数组。slice、map、function由于不能使用 == 来判断,不能作为map的key。
1.2 map的使用
通过key操作元素:
var numbers map[string]int
numbers = make(map[string]int)
numbers["one"] = 1                     //赋值
numbers["ten"] = 10                 //赋值
numbers["three"] = 3
delete(numbers, "ten")                 // 删除key为 ten 的元素
fmt.Println("第三个数字是: ", numbers["three"])     // 读取数据
map的遍历:同数组一样,使用for-range 的结构遍历
注意:
- map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取;
 - map的长度是不固定的,也就是和slice一样,也是一种引用类型
 - 内置的len函数同样适用于map,返回map拥有的key的数量
 - go没有提供清空元素的方法,可以重新make一个新的map,不用担心垃圾回收的效率,因为go中并行垃圾回收效率比写一个清空函数高效很多
 - map和其他基本型别不同,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制
 
1.3 并发安全的map
演示并发读写map的问题:
package main
func main() {
    m := make(map[int]int)
    go func() {            
        for {                //无限写入
            m[1] = 1
        }
    }()
    go func() {
        for {                //无限读取
            _ = m[1]
        }
    }()
    for {}                    //无限循环,让并发程序在后台执行
}
编译会有错误提示:fatal error: concurrent map read and map write,即出现了并发读写,因为用两个并发程序不断的对map进行读和写,产生了竞态问题。map内部会对这种错误进行检查并提前发现。   
Go内置的map只有读是线程安全的,读写是线程不安全的。
需要并发读写时,一般都是加锁,但是这样做性能不高,在go1.9版本中提供了更高效并发安全的sync.Map。
sync.Map的特点:
- 无须初始化,直接声明即可
 - sync.Map不能使用map的方式进行取值和设值操作,而是使用sync.Map的方法进行调用。Store表示存储,Load表示获取,Delete表示删除。
 - 使用Range配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,需要继续迭代时,返回true,终止迭代返回false。
 
package main
import (
    "fmt"
    "sync"
)
func main() {
    var scene sync.Map
    //保存键值对
    scene.Store("id",1)
    scene.Store("name","lisi")
    //根据键取值
    fmt.Println(scene.Load("name"))            
    //遍历
    scene.Range(func(k, v interface{}) bool{
        fmt.Println(k,v)
        return true
    })
}
注意:map没有提供获取map数量的方法,可以在遍历时手动计算。sync.Map为了并发安全。损失了一定的性能。