12、Golang 教程 - Go 映射 map

  • 映射是一种数据结构,用于存储一系列无序的键值对(映射基于键来存储值)。
  • 映射功能强大的地方是,能够基于键快速检索数据。键就像索引一样,指向与该键关联的值。
  • 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 语法如下

 // 创建一个映射,键的类型 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)