Map是一种无序的键值对的集合,这是一种通过key来快速获取、更新或移除键对应的值的结构。
Go的Map是用Hash表实现的,无论Map有多大,这些操作都可在一个常量时间内就能完成。
Go语言中定义map类型时需要指明键类型和值类型,map[k]V,其中k和V是键和值的类型,键的类型k必须是可以通过==操作符来比较的数据类型。
我们可以像迭代数组和切片那样迭代Map,但是因为Map是无序的,所以我们在迭代时无法确定元素顺序。
// name 变量名称, k 键的数据类型, V 值的数据类型
var name map[k]V
上面的语句声明了map型变量,该map的所有键都是k类型,所有值都是V类型。由于没有显示初始化,该变量的值被编译器初始化为nil。
Key类型可以是任意支持==或者!=操作符比较的类型,比如string、int、float。因为数组、切片、和结构体不支持==和!=操作符比较,所以他们不能作为map的Key,但指针和接口类型可以。
Value类型可以是任何类型。
示例:
var m1 map[string]int
var m2 map[int]string
var m3 map[string][]byte
通过Map字面量创建,例如:
var m1 = map[string]int {
"hello": 101,
"world": 102,
"i": 103,
"love": 104,
"go": 105,
}
var m2 = map[string]int{} // 空map,与nil map不同
通过make()函数创建,例如:
var m1 = make(map[string]int) // 空map
通过键来访问对应的值。
var m1 = map[string]int {
"hello": 101,
"world": 102,
"i": 103,
"love": 104,
"go": 105,
}
var cnt int
// 获取键的值:
// 如果键存在,返回对应的值
cnt = m1["hello"]
fmt.Println(cnt) // 101
// 如果键不存在,返回值类型的零值
cnt = m1["monday"]
fmt.Println(cnt) // 0 -- 得到该类型的零值
// 向键赋值:
// 如果键存在,则修改其值
m1["hello"] = 30
fmt.Println(m1["hello"]) // 30
// 如果键不存在,则新增这个键值对
m1["sunday"] = 40
fmt.Println(m1["sunday"]) // 40
如果我们获取一个不存在的键的值,将会得到其零值。这就导致我们不知道到底是不存在该键、存在该键但值为零值。为了解决这个问题,Go为我们提供了一个检测键是否存在的方法:
var m1 = map[string]int {
"hello": 101,
"world": 102,
"i": 103,
"love": 104,
"go": 105,
}
cnt, ok := m1["monday"]
fmt.Println(cnt, ok) // 0 false
cnt, ok = m1["hello"]
fmt.Println(cnt, ok) // 101 true
使用Go内置的函数delete()。
var m1 = map[string]int {
"hello": 101,
"world": 102,
"i": 103,
"love": 104,
"go": 105,
}
delete(m1, "hello")
cnt, ok = m1["hello"]
fmt.Println(cnt, ok) // 0 false
fmt.Println(m1) // map[go:105 i:103 love:104 world:102]
delete()函数接受两个参数,第一个是要操作的map,第二个是要删除的键。
delete()函数删除不存在的键也是可以的,只是不其任何作用而已。
使用for-range遍历:
var m1 = map[string]int {
"hello": 101,
"world": 102,
"i": 103,
"love": 104,
"go": 105,
}
for k, v := range m1 {
fmt.Println(k, v)
}
// 输出
// hello 101
// world 102
// i 103
// love 104
// go 105
range返回两个值,第一个是键,第二个是值。
这种遍历是无序的。如果想按序遍历,可以先把map中的键都取出来,排序后再按键遍历。例如:
package main
import (
"fmt"
"sort"
)
func main() {
var m1 = map[string]int {
"hello": 101,
"world": 102,
"i": 103,
"love": 104,
"go": 105,
}
var keys []string
for k := range m1 {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, m1[k])
}
}
// 输出
// go 105
// hello 101
// i 103
// love 104
// world 102
map可以根据新增的键值对动态的伸缩,它的容量不存在限制。可以在创建map的时候指定初始容量。例如:
m1 := make(map[string]int, 100)
当map中的键值对达到容量上限时,如果在新增键值对,map的容量会自动扩展。
Map类型是引用类型,让我们通过下面的代码看一下Map类型变量占用多少内存:
package main
import (
"fmt"
"unsafe"
)
func main() {
var m1 = map[string]int {
"hello": 101,
"world": 102,
"i": 103,
"love": 104,
"go": 105,
}
fmt.Println(unsafe.Sizeof(m1)) // 输出 8
}
由上面的输出可知,map类型的变量仅占8字节(在64位系统上是8字节,在32位系统上是4字节)。事实上,在Go语言中声明一个map型变量,该变量的内存结构中仅仅包含一个地址,这个地址指向了真正的map对象。
当把一个map型变量赋值给别的变量,或者将一个map变量传递给函数参数时,复制的也仅仅是上图中的那个地址。也就是说,当把一个map传给一个函数,函数内的map和函数外的map引用的是同一个map对象。
func main() {
var m1 = map[string]int {
"hello": 101,
"world": 102,
"i": 103,
"love": 104,
"go": 105,
}
myFunction(m1)
fmt.Println(m1["hello"]) // 100
fmt.Printf("main函数中m1指向的map对象的地址:%p\n", m1) // main函数中m1指向的map对象的地址:0xc0000b6330
fmt.Printf("main函数中m1变量的地址:%p\n", &m1) // main函数中m1变量的地址:0xc0000cc018
}
func myFunction(m map[string]int) {
m["hello"] = 100
fmt.Printf("myFunction函数中map对象的地址:%p\n", m) // myFunction函数中map对象的地址:0xc0000b6330
fmt.Printf("myFunction函数中m变量的地址:%p\n", &m) // myFunction函数中m变量的地址:0xc0000cc020
}
上面的这个例子中,main函数中的map变量和myFunction函数中的map变量的地址是不同的,但这两个函数中的map变量所指向的map对象的地址是一样的。因此myFunction函数内对map的修改对main函数中的map也生效。
大多数的map操作都可以安全的在map的nil上执行,包括查找元素,删除元素,获取map元素个数,执行range循环,这时和操作空map的结果一致。但是向nil map中设置键值会导致宕机异常:
var m1 map[string]int
fmt.Println(m1["hello"]) // 0
delete(m1, "hello")
fmt.Println(len(m1)) // 0
for k, v := range m1 {
fmt.Println(k, v)
}
// m1["hello"] = 100 // 运行时发生异常:panic: assignment to entry in nil map
m1 := make(map[string]int)
m1["hello"] = 200
_ = &m1["hello"] // 编译错误: cannot take the address of m1["hello"]
原因有二:
Go语言没有提供集合类型,但通过Map类型很容易实现集合。即:利用Map类型的键来存储集合元素。
下面的例子实现了一个字符串集合:
set := make(map[string]bool)
set["apple"] = true
set["orange"] = true
set["banana"] = true
if set["apple"] {
fmt.Println("apple in set")
}
if set["cherry"] {
fmt.Println("cherry in set")
}
delete(set, "apple")
if set["apple"] {
fmt.Println("apple in set")
}
Copyright@2022 , [email protected]