- 映射是一种数据结构,用于存储一系列无序的键值对(映射基于键来存储值)。
- 映射功能强大的地方是,能够基于键快速检索数据。键就像索引一样,指向与该键关联的值。
- Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
- map 是引用类型,遵守引用类型传递的机制,在一个函数接收 map,修改后,会直接修改原来的 map。
- 容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动态的增长键值对。
- map 的 value 经常使用 struct 类型,更适合管理复杂的数据(比 value 是一个 map 更好),比如 value 为 Student/Teacher 结构体。
- 与 C++、Java 不一样,Go 使用映射(map)不需要引入任何库。
- 四种复合类型特点:
复合类型 | 值的类型 | 值的变量 | 值的索引 |
---|---|---|---|
数组 | 相同 | 固定 | 下标 |
切片 | 相同 | 动态 | 下标 |
结构体 | 不相同 | 固定 | 属性名 |
映射 | 相同 | 动态 | key 键 |
1. 映射的实现
因为映射也是一个数据集合,所以也可以使用类似处理数组和切片的方式来迭代映射中的元素。但映射是无序集合,所以即使以同样的顺序保存键值对,每次迭代映射时,元素顺序也可能不一样。无序的原因是映射的本质使用了散列表(hash)。
map 在底层是用哈希(hash)表实现的,在 C:\Program Files\Go\src\hash\maphash\maphash.go
,map 是一个 hash 数组列表,由一个个 bucket 组成,示意图如下:
每一个元素都被称为 bucket 的结构体,每一个 bucket 可以保存 8 个键值对,所有元素被 hash 算法填入到数组的 bucket 中,bucket 填满后,将通过一个 overflow 指针来扩展一个 bucket,从来形成链表,以此来解决 hash 冲突的问题,map 就是一个 bucket 指针型的一维数组。
创建 map 语法如下
// 创建一个映射,键的类型 string,值的类型 int
dict := make(map[string]int)
dict := make(map[string]int,<元素数量>,)
// 创建一个映射,键值类型都是 string,并对两个键值对进行初始化
dict := map[string]string{
"name":"zhangsan","address":"nanjing"}
映射的键可以是任何值,这个值的类型并不限制,内置类型或者结构体都可以,需要确定这个值可以使用运算符做比较。需要注意的是,切片、函数以及包含切片的结构类型由于是引用类型,均不能作为映射的键。
dict := map[[]string]int{
} // 报错:incomparable map key type []string
dict := map[int][]string{
} // 切片可以作为值,不可以作为键
map 三种使用方式
//方式一
//不赋值就需要使用 make() 开辟内存空间,不然会变成 nil map
var a map[string]string
a = make(map[string]string,10)
a["name"]="Marry"
//方式二
cities := make(map[string]string)
cities["no1"]="北京"
cities["no2"]="上海"
cities["no3"]="赣州"
//方式三
names := map[string]string{
"name1":"小明",
"name2":"小花",
"name3":"小李",
}
示例
package main
import "fmt"
func main() {
// 映射声明
// disc := make(map[string]string)
dict := map[string]string{
"name":"zhangsan","address":"nanjing"}
fmt.Println(dict)
if len(dict) == 0 {
fmt.Println("空映射")
} else {
fmt.Println("元素个数",len(dict))
}
}
// 结果
map[address:nanjing name:zhangsan]
元素个数 2
2. 元素赋值与添加
- map 添加数据只能用键值对赋值的方式,没有 append 函数,因为 map 是无序的,也不存在往中间添加数据一说。
- 使用 map[key]=value 这样的表达式添加数据,如果有存在的 key 会覆盖对应的 value。
// 创建一个空映射,用来存储颜色以及颜色对应的十六进制代码
colors := map[string]string{
}
// 将 red 的代码加入到映射
colors["red"] = "#DA1337"
与切片类似,通过声明一个未初始化的映射可以创建一个值为 nil 的映射。nil 映射不能用于存储键值对,否则会产生运行错误。
// 创建一个 nil 映射
var colors map[string]string
// 将 red 的代码加入到映射
colors["red"] = "#da1337"
// 运行报错
panic: assignment to entry in nil map
示例
package main
import "fmt"
// 映射赋值
func main() {
colors := map[string]string{
}
// 赋值直接定义键值对,自动添加,键名必须唯一
// 声明空映射
// var colors map[string]string
colors["red"] = "红色"
colors["blue"] = "蓝色"
// colors["red"] = "红"
// colors[""] = "无色"
// map 的 key 是不能重复的,如果重复了,则以最后这个 key-value 为准
fmt.Println(colors)
}
// 结果
map[blue:蓝色 red:红色]
如果不想出现覆盖的情况,可以判断一下键是否存在再绝对是否添加元素。
if _,ok := map[key];!ok{
map[key]=value
}
// -------------------------- //
Map1 := make(map[string]string)
countryCapitalMap := make(map[string]string)
Map1["France"] = "巴黎"
Map1["Italy"] = "罗马"
Map1["Japan"] = "东京"
Map1["India"] = "新德里"
Map1["Usa"] = "华盛顿"
for key, value := range countryCapitalMap {
if v, ok := Map1[key]; !ok {
Map1[key] = value
}
}
3. 查找与遍历
从映射取值时有两种方式。
- 第一种方式是获得值以及一个表达这个值是否存在的标志。
package main
import "fmt"
// 映射赋值
func main() {
colors := map[string]string{
}
colors["red"] = "红色"
colors["blue"] = "蓝色"
// 第一种方式获取并且判断值是否存在
value,exe := colors["blue"]
if exe {
fmt.Println(value)
} else {
fmt.Println("不存在")
}
}
// 结果
蓝色
- 第二种方式是只返回键对应的值,再判断这个值是否有零值,以此来确定键是否存在。这种方式只能用在映射存储的值都是非零值的情况。
package main
import "fmt"
func main() {
// 第二种方式获取值
disc := map[string]int{
"1":10,"2":20,"3":30}
value := disc["2"]
if value != 0 {
fmt.Println(value)
} else {
fmt.Println("不存在")
}
}
// 结果
20
循环遍历访问 map 元素
// range 可以迭代映射里的所有值
package main
import "fmt"
func main() {
disc := map[string]int{
"1":10,"2":20,"3":30,"4":40,"5":50}
// 遍历 map 中所有值
for k,v := range disc {
fmt.Printf("key=%s,value=%d\n",k,v)
}
}
// 结果
key=1,value=10
key=2,value=20
key=3,value=30
key=4,value=40
key=5,value=50
4. 元素删除
内置函数 delete() 用于删除容器内的元素。
delete(myMap,"1234")
从myMap 中删除键为 1234 的键值对。如果 1234 这个键不存在,那么这个调用将什么都不发生,也不会有任何副作用。但是如果传入的 map 变量的值是 nil,该调用将导致程序抛出异常(panic)。
示例
package main
import "fmt"
func main() {
disc := map[string]int{
"1":10,"2":20,"3":30,"4":40,"5":50}
// 遍历 map 中所有值
for k,v := range disc {
fmt.Printf("key=%s,value=%d\n",k,v)
}
fmt.Println("-----------删除后-----------")
// 删除元素
delete(disc,"2")
for k,v := range disc {
fmt.Printf("key=%s,value=%d\n",k,v)
}
}
// 结果
key=3,value=30
key=4,value=40
key=5,value=50
key=1,value=10
key=2,value=20
-----------删除后-----------
key=1,value=10
key=3,value=30
key=4,value=40
key=5,value=50
5. 将映射传递给函数
在函数间传递映射并不会制造出该映射的副本。当传递映射给函数,并对这个映射做了修改时,所有对这个映射的引用都会察觉到这个修改(map 是引用类型。传递的是地址)。
package main
import "fmt"
func main() {
disc := map[string]int{
"1":10,"2":20,"3":30,"4":40,"5":50}
// 遍历 map 中所有值
for k,v := range disc {
fmt.Printf("key=%s,value=%d\n",k,v)
}
// 映射传参处理
test(disc)
fmt.Println("-----------处理后-----------")
for k,v := range disc {
fmt.Printf("key=%s,value=%d\n",k,v)
}
}
func test(m map[string]int) {
m["2"] = 100
}
// 结果
key=2,value=20
key=3,value=30
key=4,value=40
key=5,value=50
key=1,value=10
-----------处理后-----------
key=1,value=10
key=2,value=100 // map 是引用类型
key=3,value=30
key=4,value=40
key=5,value=50
6. value 嵌套 map
https://www.cnblogs.com/PatrickStarGazer/p/15971483.html
// 存放两个学生信息,每个学生有 name,sex 和 address 信息
package main
import "fmt"
func main(){
students := make(map[string]map[string]string)
// 01 和 02 都是键,3 为元素数量(可省略)
students["01"]=make(map[string]string,3)
students["01"]["name"]="tom"
students["01"]["sex"]="男"
students["01"]["address"]="rode1"
students["02"]=make(map[string]string,3)
students["02"]["name"]="Marry"
students["02"]["sex"]="女"
students["02"]["address"]="rode2"
fmt.Println(students)
fmt.Println(students["02"]["name"]) //取出其中一个信息
}
// 结果
// 0x 键对应的 map value 值
map[01:map[address:rode1 name:tom sex:男] 02:map[address:rode2 name:Marry sex:女]]
Marry
7. value 嵌套切片
map 的 value 可以是任意类型,例如嵌套一个 slice 到 map 中|
func main() {
testmap := map[string][]string{
}
testmap["1"] = []string{
"001", "002"}
fmt.Println(testmap["1"])
}
// [001 002]
- 综合示例
// 使用 map 来记录学生信息,name 和 age
// 一个学生对应一个 map,并且学生的个数可以动态增加
package main
import "fmt"
func main(){
// 注意,切片的数据类型为 map
var students []map[string]string
students = make([]map[string]string,2) //切片本身需要 make
//增加第一个学生信息
if students[0]==nil {
students[0]=make(map[string]string,2)
students[0]["name"]="Marry"
students[0]["age"]="18"
}
//增加第二个学生信息
if students[1]==nil {
students[1]=make(map[string]string,2)
students[1]["name"]="Jack"
students[1]["age"]="20"
}
//使用切片的 append 函数,动态增加学生个数
newstudent := map[string]string {
"name":"新的学生Tom",
"age":"16",
}
students = append(students,newstudent)
fmt.Println(students)
}
// 结果
[map[age:18 name:Marry] map[age:20 name:Jack] map[age:16 name:新的学生Tom]]
package main
import "fmt"
func main() {
// 元素类型为 map 的切片: [map, map, map]
// 先初始化切片, 因为后续需要用索引初始化 map, 所以要给长度
a := make([]map[string]int, 2)
// 使用切片的索引对 map 进行初始化
a[0] = make(map[string]int, 2)
// 赋值
a[0]["Tom"] = 100
a[0]["Tim"] = 99
fmt.Println(a) // [map[Tim:99 Tom:100] map[]], 这里因为长度为 2, 用零值补齐, 所以会有 map[]
}
- 值为切片的 map
package main
import "fmt"
func main() {
// 值为切片的 map: map[string][]int, 例: ["语文"][100,99]
// 先初始化外层的 map
a := make(map[string][]int, 2)
// 再初始化内层的切片
a["语文"] = make([]int, 0, 2)
// 给切片赋值
a["语文"] = append(a["语文"], 100, 99)
a["数学"] = make([]int, 0, 2)
a["数学"] = append(a["数学"], 97, 93)
fmt.Println(a)
}
- 统计字符串中出现的字符次数
package main
import (
"fmt"
"strings"
)
func main() {
s := "how do you do"
// 得到一个切片
words := strings.Split(s, " ")
// 遍历这个切片, 如果字母存在, v+1, 如果字母不存在, 创建
a := make(map[string]int, 10) // 创建一个map 用于接收
// 遍历 words 切片, 把每个单词拿出来
for _, word := range words {
// 在 map 中查找, 如果存在, 计数+1, 如果不存在, 则计数=1
v, ok := a[word]
if ok {
a[word] = v + 1
} else {
a[word] = 1
}
}
fmt.Println(a) // map[do:2 how:1 you:1]
}
8. value 嵌套结构体
type aa struct {
a string
b int64
c string
d int64
}
func main() {
as := map[int64]*aa{
11: &aa{
a: "A",
b: 1,
c: "",
d: 0,
},
}
for index, v := range as {
//type sf map[int64]aa
//var aaa sf = map[int64]aa{
// index: aa{
// c: "nn",
// },
//}
v.c = "nn"
fmt.Printf("%#v", as[index])
}
//fmt.Println(as)
//os.Exit(12)
for _, v := range as {
fmt.Println(v)
}
os.Exit(124)
map 和 struct 的多重嵌套
struct Node1
{
int data1;
int data2;
};
struct Node
{
int key;
map<int,Node1> myMap1;
};
//map中有Node,Node中有myMap1,myMap1中有Node1,则要想myMap中插入元素,必须由里到外赋值或插入
int main()
{
//向myMap中插入元素
map<int,Node> myMap;
map<int, Node>::iterator it;
map<int, Node1>::iterator it1;
int p1 = 4,p=5;
Node1 N1 = {
1,2};
Node N;
N.key = 3;
N.myMap1.insert(pair<int, Node1>(p1, N1));
myMap.insert(pair<int, Node>(p, N));
//遍历myMap
for (it = myMap.begin(); it != myMap.end(); it++)
{
printf("%d\n",it->first);
printf("%d\n", it->second.key);
for (it1 = it->second.myMap1.begin(); it1 != it->second.myMap1.end(); it1++)
{
printf("%d\n", it1->first);
printf("%d\n", it1->second.data1);
printf("%d\n", it1->second.data2);
}
}
return 0;
}
struct 作为 map 的 key
type student struct {
name string
age int
}
stumap := map[student]int{
{
"zhangsan", 18}: 1,
}
//key 是数组
stumap2 := map[[2]int]int{
{
1, 2}: 1}
fmt.Println(stumap2)