Go
中字典也叫做 map
, map
是一种无序的键值对的集合。 map
最重要的一点是通过 key
来快速检索数据, key
类似于索引,指向数据的值。
1. 字典定义
可以使用内建函数 make
也可以使用 map
关键字来定义 map
:
/* 声明变量,默认 map 是 nil */
var mapName map[mapKey]mapValue
/* 使用 make 函数 */
mapName := make(map[mapKey]mapValue)
这两者区别在:
-
直接用
var
声明,只是声明了变量mapName
,但是没有初始化内存,如果直接写入会导致出现 nil map 继而导致恐慌(panic)事; -
使用内置函数
make
,会声明并初始化一个哈希映射数据结构,并返回一个指向它的映射值。这是正确的初始化map
的方法。
定义一个字典类型变量语法格式有以下多种写法:
var mapName map[mapKey]dataType
var mapName map[mapKey]dataType = make(map[mapKey]dataType)
var mapName = make(map[mapKey]dataType)
var mapName map[mapKey]dataType = map[mapKey]dataType{}
var mapName = map[mapKey]dataType{}
mapName := map[mapKey]dataType{}
mapName := make(map[mapKey]dataType)
// 创建一个映射,键的类型是string,值的类型是int
dict := make(map[string]int) // map 容量使用默认值
dict := make(map[string]int, len) // map 容量使用给定的 len 值
// 创建一个映射,键和值的类型都是string
// 使用两个键值对初始化映射
dict := map[string]string{"country": "China", "province": "Shaanxi"}
2. 字典初始化
如果不初始化 map
,那么就会创建一个 nil map
。 nil map
不能用来存放键值对。
可以通过声明一个未初始化的映射来创建一个值为 nil
的映射(称为 nil
映射)。
nil
映射不能用于存储键值对,否则,会产生一个语言运行时错误,如下
// 通过声明映射创建一个nil映射
var book map[string]string
book["author"] = "wohu"
Runtime Error:
panic: runtime error: assignment to entry in nil map
但是使用内置函数 make
创建的 map
可以用于存储键值对。
book := make(map[string]string)
book["author"] = "wohu"
fmt.Printf("book is %v\n", book) // book is map[author:wohu]
fmt.Printf("book is %v\n", book["author"]) // book is wohu
或者使用如下赋值
d1 := make(map[string]string)
var d2 = map[string]string{} // 注意:后面带了个大括号
d1["age"] = "18"
d2["name"] = "wohu"
fmt.Printf("d1 is %v\n", d1) // d1 is map[age:18]
fmt.Printf("d2 is %v\n", d2) // d2 is map[name:wohu]
使用函数 len()
可以获取 map
中 pair 的数目。
3. 字典键类型约束
Go
语言字典的键类型不可以是函数类型、字典类型和切片类型。
Go
语言规范规定,在键类型的值之间必须可以施加操作符 ==
和 !=
。换句话说,键类型的值必须要支持判等操作。由于函数类型、字典类型和切片类型的值并不支持判等操作,所以字典的键类型不能是这些类型。
Go
语言中要求,key
的类型必须支持 ==
和 !=
两种比较操作符。而函数类型、map
类型自身,以及切片只支持与 nil
的比较,而不支持同类型两个变量的比较。
s1 := make([]int, 1)
s2 := make([]int, 2)
f1 := func() {}
f2 := func() {}
m1 := make(map[int]string)
m2 := make(map[int]string)
println(s1 == s2) // 错误:invalid operation: s1 == s2 (slice can only be compared to nil)
println(f1 == f2) // 错误:invalid operation: f1 == f2 (func can only be compared to nil)
println(m1 == m2) // 错误:invalid operation: m1 == m2 (map can only be compared to nil)
所以函数类型、map
类型自身,以及切片类型是不能作为 map
的 key
类型的。
另外,如果键的类型是接口类型的,那么键值的实际类型也不能是上述三种类型,否则在程序运行过程中会引发 panic
(即运行时恐慌)。
4. 遍历字典
注意:遍历输出元素的顺序与填充顺序无关,不能期望 map
在遍历时返回某种期望顺序的结果。
package main
import "fmt"
func main() {
book := make(map[string]string)
book["price"] = "100"
book["author"] = "wohu"
book["language"] = "Chinese"
for k := range book { // 迭代 key, 不能保证每次迭代元素的顺序
fmt.Println(k, "value is ", book[k])
}
for k, v := range book { // 同时迭代 key 和 value
fmt.Println(k, v)
}
/*查看元素在集合中是否存在 */
published, ok := book["published"] /*如果确定是真实的,则存在,否则不存在 */
/*fmt.Println(published) */
/*fmt.Println(ok) */
if ok {
fmt.Println("published is ", published)
} else {
fmt.Println("published not exist")
}
}
运行结果为:
price value is 100
author value is wohu
language value is Chinese
price 100
author wohu
language Chinese
published not exist
读取字典,就是根据 key
值,在字典中查找 key
对应的 value
值,读取字典通常有两种方法,分别是:
// 第一种情况,断言查询,推荐使用这种方法
val,ok := map[key]
// 第二种情况直接查询
val := map[key]
上边两种读取字典的方法中,
- 第一种采用断言的方式,当
ok
为true
时,表示key
存在于字典中,当ok
为false
时,表示key
不存在于字典中; - 第二种情况直接查询
key
值的方式,如果key
不在map
种时,返回值是字典定义中value
的类型默认初始值。如value
的类型为int
,则默认值为 0,如果value
类型是指针,则默认是nil
;
5. 删除 map 元素
Go
语言提供了一个内置函数 delete()
,用于删除容器内的元素,使用 delete()
内建函数从 map
中删除一组键值对, delete()
函数的格式如下:
delete(map, key)
其中 map
为要删除的 map
实例, key
为要删除的 map
中键值对的键。
package main
import "fmt"
func main() {
book := map[string]string{
"price": "100",
"author": "wohu",
"language": "Chinese", // 数组或者字典在多行编写时必须要在最后一个元素后面加逗号
}
// syntax error: unexpected newline, expecting comma or }
for k, v := range book {
fmt.Println("k is ", k, ",v is ", v)
}
delete(book, "author")
for k, v := range book {
fmt.Println("k is ", k, ",v is ", v)
}
mapLength := len(book)
fmt.Println("book length is ", mapLength)
}
delete
函数是从 map
中删除键的唯一方法。即便传给 delete
的键在 map
中并不存在,delete
函数的执行也不会失败,更不会抛出运行时的异常。
6. 清空 map
Go
语言中并没有为 map
提供任何清空所有元素的函数、方法,清空 map
的唯一办法就是重新 make
一个新的 map
,不用担心垃圾回收的效率, Go
语言中的并行垃圾回收效率比写一个清空函数要高效的多。
7. 用切片作为 map 的值
既然一个 key
只能对应一个 value
,而 value
又是一个原始类型,那么如果一个 key
要对应多个值怎么办?
通过将 value 定义为 []int
类型或者其他类型的切片,就可以优雅的解决这个问题,示例代码如下所示:
mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)
8. map 的有序遍历
8.1 先对key进行排序,再根据 key 进行遍历
Go
中没有一个专门的方法针对map
的key
进行排序Go
中的map
默认是无序的,注意也不是按照添加的顺序存放的,你每次遍历,得到的输出可能不一样Go
中map
的排序,是先将key
进行排序,然后根据key
值遍历输出即可
package main
import (
"fmt"
"sort"
)
func main() {
//1.定义一个key打乱的字典
person := map[int]string{3: "张学友", 1: "刘德华", 2: "郭富城", 4: "黎明", 5: "我"}
//2.定义一个切片
s := make([]int, 0, len(person))
//3.遍历map获取key-->s1中
for key := range person {
s = append(s, key)
}
//4.给s进行排序
sort.Ints(s)
// sort.Strings(a)
//5. 遍历s 来读取 person
for _, k := range s { // 先下标,再数值
fmt.Println(k, person[k])
}
}
输出:
1 刘德华
2 郭富城
3 张学友
4 黎明
5 我
8.2 使用 url.Values{} 进行排序
url.Values{}
引用 Go
标准库 http/url
,查看库,可以看到 url.Values
的定义如下:
type Values map[string][]string
参见:https://blog.csdn.net/wohu1104/article/details/106629308
从定义可以看出,本质上 map
的扩展方法。我们常用的有 Set
、Add
、Del
、Encode
等函数。
package main
import (
"fmt"
"net/url"
)
func main() {
v := url.Values{}
v.Add("1", "一")
v.Add("2", "二")
v.Add("3", "三")
v.Add("4", "四")
v.Add("5", "五")
fmt.Printf("%#v", v)
}
9. map 变量的传递开销
和切片类型一样,map
也是引用类型。这就意味着 map
类型变量作为参数被传递给函数或方法的时候,实质上传递的只是一个“描述符”,而不是整个 map
的数据拷贝,所以这个传递的开销是固定的,而且也很小。
并且,当 map
变量被传递到函数或方法内部后,我们在函数内部对 map
类型参数的修改在函数外部也是可见的。
package main
import "fmt"
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]
}