map是Go语言提供的一种抽象数据类型,它表示一组无序的键值对。

map类型不支持“零值可用”,未显式赋初值的map类型变量的零值为nil。对处于零值状态的map变量进行操作将会导致运行时panic:

1
2
var m map[string]int // m = nil
m["key"] = 1         // panic: assignment to entry in nil map

我们必须对map类型变量进行显式初始化后才能使用它。和切片一样,创建map类型变量有两种方式:一种是使用复合字面值,另一种是使用make这个预声明的内置函数。

  1. 使用复合字面值

    1
    2
    3
    4
    
    userInfo := map[string]string{
    		"username": "Jack",
    		"password": "123456",
    	}
    
  2. 使用 make()

    1
    
    scoreMap := make(map[string]int, 8)
    

和切片一样,map也是引用类型,将map类型变量作为函数参数传入不会有很大的性能损耗,并且在函数内部对map变量的修改在函数外部也是可见的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func foo(m map[string]int) {
    m["key1"] = 11
    m["key2"] = 12
}

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

    fmt.Println(m) // map[key1:1 key2:2]
    foo(m)
    fmt.Println(m) // map[key1:11 key2:12]
}

map 基本操作

  1. 插入数据

    面对一个非nil的map类型变量,我们可以向其中插入符合map类型定义的任意键值对。Go运行时会负责map内部的内存管理,因此除非是系统内存耗尽,我们不用担心向map中插入数据的数量。

    1
    2
    3
    4
    
    m := make(map[K]V)
    m[k1] = v1
    m[k2] = v2
    m[k3] = v3
    

    如果key已经存在于map中,则该操作会用新值覆盖旧值。

  2. 获取数据个数

    和切片一样,map也可以通过内置函数len获取当前已经存储的数据个数

  3. 查询和数据读取

    map类型更多用在查找和数据读取场合。所谓查找就是判断某个key是否存在于某个map中。我们可以使用“comma ok”惯用法来进行查找:

    1
    
    value, ok := map[key]
    

    例子:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    func main() {
    	scoreMap := make(map[string]int)
    	scoreMap["张三"] = 90
    	scoreMap["李四"] = 100
    	// 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值
    	v, ok := scoreMap["张三"]
    	if ok {
    		fmt.Println(v)
    	} else {
    		fmt.Println("查无此人")
    	}
    }
    
  4. 删除元素

    使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:

    1
    
    delete(map, key)
    
    • map:表示要删除键值对的map
    • key:表示要删除的键值对的键
  5. 遍历数据

    我们可以像对待切片那样通过for range语句对map中的数据进行遍历

    1
    2
    3
    
    for k,v := range scoreMap{
    		fmt.Println(k, v)
    }
    

    Go运行时在初始化map迭代器时对起始位置做了随机处理。因此千万不要依赖遍历map所得到的元素次序。

    如果你需要一个稳定的遍历次序,那么一个比较通用的做法是使用另一种数据结构来按需要的次序保存key,比如切片:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    func main() {
    	rand.Seed(time.Now().UnixNano()) //初始化随机数种子
    
    	var scoreMap = make(map[string]int, 200)
    
    	for i := 0; i < 100; i++ {
    		key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
    		value := rand.Intn(100)          //生成0~99的随机整数
    		scoreMap[key] = value
    	}
    	//取出map中的所有key存入切片keys
    	var keys = make([]string, 0, 200)
    	for key := range scoreMap {
    		keys = append(keys, key)
    	}
    	//对切片进行排序
    	sort.Strings(keys)
    	//按照排序后的key遍历map
    	for _, key := range keys {
    		fmt.Println(key, scoreMap[key])
    	}
    }
    

    小结:

    和切片一样,map是Go语言提供的重要数据类型,也是日常编码中最常使用的类型之一。

    我们在日常使用map的场合要把握住下面几个要点:

    • 不要依赖map的元素遍历顺序;
    • map不是线程安全的,不支持并发写;
    • 不要尝试获取map中元素(value)的地址;
    • 尽量使用cap参数创建map,以提升map平均访问性能,减少频繁扩容带来的不必要损耗。