Go 学习笔记(12):map

创建

Map 是一个无序的 key/value 对的集合,其中 key 是唯一的。如果我们在向字典中放入一个键值对的时候其中已经有相同的键的话,那么与此键关联的那个值会被新值替换。

map 的字面量是 map[K]T,其中

  • K:键的类型
  • T:元素(或称值)的类型

注意:键类型必须是支持 == 比较运算符的数据类型,否则会引起错误。即不能是数组、切片、字典、结构体、函数类型,但是指针和接口类型可以。对于值的类型,没有限制。

map 可以通过测试 key 是否相等来判断是否已经存在。虽然浮点数类型也是支持相等运算符比较的,但最好别使用将浮点数作为 key ,可能出现 NaN 和任何浮点数都不相等。

含有数组切片的结构体不能作为 key,只包含内建类型的 struct 是可以作为 key 的。如果要用结构体作为 key 可以提供 Key()Hash() 方法,这样可以通过结构体的域计算出唯一的数字或者字符串的 key

示例:

var m1 map[string]int
m1 = map[string]int{"a": 1, "b": 2}

var m2 map[string]int = map[string]int{}
m2["a"] = 3
m2["b"] = 4

m3 := map[string]int{"a": 1, "b": 2}

m4 := map[string]int{}
m4["a"] = 1
m4["b"] = 2
复制代码

与切片类型相同,字典类型属于引用类型。它的零值即为 nil。(详见零值问题)

map 可以根据新增的 key-value 对动态的伸缩,不存在固定长度或者最大限制。但是你可以选择标明 map 的初始容量 capacity

m1 := make(map[string]int)
m1["a"] = 1
m1["b"] =2

m2 := make(map[string]int, 100)
复制代码

不要使用 new,永远用 make 来构造 map。如果你错误的使用 new() 分配了一个引用对象,你会获得一个空引用的指针,相当于声明了一个未初始化的变量并且取了它的地址。

取值

对于 map 来说,如果获取不存在的 key,就以它的值类型的空值(或称默认值)作为值结果。那么,我们在不知道 map 确切的情况下,怎么知道要取的 key 是否存在呢?

m1 := map[string]int{"a": 1, "b": 2}

x := m1["a"]       // 1
x, ok := m1["a"]   // 1 true   _, ok := m1["a"] 可以用来判断 key 是否存在。

y := m1["c"]       // 0
y, ok := m1["c"]   // 0 false
复制代码

map 中的元素并不是一个变量,因此我们不能对 map 的元素进行取址操作。禁止对 map 元素取址的原因是 map 可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效:

&m1           // 没问题
&m1["a"]      // cannot take the address of m1["a"]
复制代码

修改

m1 := map[string]int{"a": 1, "b": 2}
m1["a"] = 3
fmt.Println(m1)  // map[a:3 b:2]

m1["c"] = 4
fmt.Println(m1)  // map[c:4 a:3 b:2]
复制代码

另外,x += yx++ 等简短赋值语法也可以用在 map

删除

delete 操作是安全的,有则删除,无则不做。

m1 := map[string]int{"a": 1, "b": 2}
delete(m1, "a")  // 有则删除
delete(m1, "c")  // 无则不做
复制代码

遍历

map 的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。在实践中,遍历的顺序是随机的,每一次遍历的顺序都不相同。这是故意的,每次都使用随机的遍历顺序可以强制要求程序不会依赖具体的哈希函数实现。

for key, value := range map1 {
    ...
}

// 如果只想获取 key,你可以这么使用:
for key := range map1 {
    fmt.Printf("key is: %d\n", key)
}
复制代码

排序

map 是无序的,如果你想按顺序遍历 map,需要将 key(或者 value)拷贝到一个切片,再对切片排序(使用 sort 包):

package main

import (
    "fmt"
    "sort"
)

var m = map[string]int{
    "d": 4,
    "a": 1,
    "b": 2,
    "c": 3}

func main() {
    // 因为我们一开始就知道names的最终大小,因此给slice分配一个合适的大小将会更有效
    // keys := []string{}
    keys := make([]string, 0, len(m))
    for key := range m {
        keys = append(keys, key)
    }

    sort.Strings(keys)

    for _, key := range keys {
        fmt.Println(key, m[key])
    }
}
复制代码

但是如果你想要一个排序的列表,最好使用结构体切片,这样会更有效:

type name struct {
   key string
   value int
}
复制代码

切片

切片的元素是 map 类型:

package main

import "fmt"

func main() {
	mySlice := make([]map[string]int, 3)  // 分配切片
	mySlice[0] = make(map[string]int, 2)  // 分配切片中的每个map元素
	mySlice[0]["a"] = 1
	mySlice[0]["b"] = 2
	mySlice[0]["c"] = 3   // map类型自动扩展
	fmt.Println(mySlice)  // [map[a:1 b:2 c:3] map[] map[]]

	for i := range mySlice {
		mySlice[i] = make(map[string]int, 3)
		mySlice[i]["a"] = 10
		mySlice[i]["b"] = 20
		mySlice[i]["c"] = 30
	}
	fmt.Println(mySlice)  // [map[a:10 b:20 c:30] map[a:10 b:20 c:30] map[a:10 b:20 c:30]]
}
复制代码

当然,mapvalue 也可以是切片类型:

var m1 map[string][]int
复制代码

比较

slice 一样,map 之间也不能进行相等比较,唯一的例外是和 nil 进行比较。要判断两个 map 是否包含相同的 keyvalue,我们必须通过一个循环实现:

func equal(x, y map[string]int) bool {
    if len(x) != len(y) {
        return false
    }
    for k, xv := range x {
        // 我们不能简单地用xv != y[k]判断
        if yv, ok := y[k]; !ok || yv != xv {
            return false
        }
    }
    return true
}
复制代码

零值问题

map 类型的零值是 nil,也就是没有引用任何哈希表。map 上的大部分操作,包括查找、删除、lenrange 循环都可以安全工作在 nil 值的 map 上,它们的行为和一个空的 map 类似。但是向一个 nil 值的 map 存入元素将导致一个 panic 异常。在向 map 存数据前必须先创建 map

var m1 map[string]int    // nil
m2 := map[string]int{}   // 空map

fmt.Println(m1 == nil)   // true 0
fmt.Println(m2 == nil)   // false 0

m1["a"] = 1              // panic: assignment to entry in nil map
m2["a"] = 1              // 正常
复制代码

实现Set

Go 语言中并没有 set 类型,由于 map 中的 key 也是不相同的,可以将忽略 valuemap 当成 set

参考目录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值