自学内容网 自学内容网

go语言Map详解

Map

Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现

map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。

它提供了高效的查找、插入和删除操作,非常适合用于构建关联数组或字典。

Map 的基本概念

每个 map 都是一个无序的集合,通过键来唯一标识值。
键可以是任何可以比较的类型(如字符串、整数、结构体等),而值可以是任何类型。
map 是引用类型,因此在传递给函数时,函数接收到的是指向同一底层数据结构的引用。

Go 中的 map 实现细节

Go 的 map 是基于哈希表实现的,具有高效的查找、插入和删除性能。

底层实现使用桶来存储多个键值对,并通过链表解决哈希冲突问题。

map 具有自动扩容的特性,以保持性能和存储效率。

哈希函数:

每个键在插入时会被传入一个哈希函数,该函数会生成一个哈希值。这个哈希值用于确定在底层数组中的位置。
Go 使用的是非加密的哈希函数,针对不同类型(如字符串、整数等)具有不同的哈希算法。

桶(Bucket):

在 Go 的 map 中,底层数组被称为桶(bucket)。每个桶可以存储多个键值对,这解决了哈希冲突的问题。
当两个或多个键经过哈希运算后映射到同一个桶时,Go 会使用链表的方式将它们链接在该桶中。
每个桶的大小和数量会根据存储的键值对数量而动态扩展。

扩容:

当 map 中的元素数量超过一定阈值时,Go 会对 map 进行扩容。
扩容时,Go 将创建一个新的、更大的底层数组,并重新计算每个键的哈希值以确定新的位置,
然后将现有的键值对复制到新的桶中。
这种扩容策略旨在保持哈希表操作的效率,同时降低碰撞的概率。

负载因子:

负载因子是一个衡量哈希表是否需要扩展的指标,通常表示为元素数量与桶的数量之比。
在 Go 中,当负载因子超过某个阈值时,会触发扩容。

创建 Map

可以使用 make 函数或字面量来创建 map。

package main

import "fmt"

func main() {
    // 创建一个空的 map,键为字符串,值为整数
    ageMap := make(map[string]int)
    
    // 插入键值对
    ageMap["Alice"] = 25
    ageMap["Bob"] = 30

    fmt.Println("年龄Map:", ageMap) // 输出: 年龄Map: map[Alice:25 Bob:30]
}

package main

import "fmt"

func main() {
    // 使用字面量创建 map
    fruits := map[string]int{
        "苹果":  10,
        "香蕉":  20,
        "橙子":  15,
    }

    fmt.Println("水果数量:", fruits) // 输出: 水果数量: map[香蕉:20 橙子:15 苹果:10]
}

访问和修改 Map

使用键来访问或修改对应的值。

package main

import "fmt"

func main() {
    fruits := map[string]int{
        "苹果":  10,
        "香蕉":  20,
    }

    // 访问值
    appleCount := fruits["苹果"]
    fmt.Println("苹果的数量:", appleCount) // 输出: 苹果的数量: 10

    // 修改值
    fruits["苹果"] = 12
    fmt.Println("更新后的苹果数量:", fruits["苹果"]) // 输出: 更新后的苹果数量: 12
    
    // 检查某个键是否存在
    if count, exists := fruits["橙子"]; exists {
        fmt.Println("橙子的数量:", count)
    } else {
        fmt.Println("橙子不存在")
    }
}

判断键是否存在

Go语言中有个判断map中键是否存在的特殊写法,格式如下

value, ok := map[key]

func main() {
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
// 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值
v, ok := scoreMap["张三"]
if ok {
fmt.Println(v)
} else {
fmt.Println("查无此人")
}
}

删除 Map 中的元素

可以使用 delete 函数删除某个键及其对应的值。

使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:

delete(map, key)
- map:表示要删除键值对的map
- key:表示要删除的键值对的键

如果删除的键不存在,则delete()函数不会报错。

package main

import "fmt"

func main() {
    fruits := map[string]int{
        "苹果":  10,
        "香蕉":  20,
    }

    // 删除香蕉
    delete(fruits, "香蕉")
    fmt.Println("删除后的水果数量:", fruits) // 输出: 删除后的水果数量: map[苹果:10]
}


遍历 Map

可以使用 for range 循环遍历 map 的所有键值对。

遍历map时的元素顺序与添加键值对的顺序无关。

package main

import "fmt"

func main() {
    fruits := map[string]int{
        "苹果":  10,
        "香蕉":  20,
        "橙子":  15,
    }

    // 遍历 map
    for fruit, count := range fruits {
        fmt.Printf("%s 的数量是: %d\n", fruit, count)
    }
}

//只遍历key
for key := range fruits {
    fmt.Println(key)
}

//只遍历value
for _, value := range fruits {
    fmt.Println(value)
}

按照指定顺序遍历map

  1. 将 map 中的键提取到切片中。
  2. 对切片进行排序。
  3. 按照排序后的键顺序遍历 map。

逻辑步骤概述
提取键:我们使用 for key := range fruits 迭代 map 的键,并将这些键添加到切片 keys 中。
排序:使用 sort.Strings(keys) 对键进行排序。根据具体的键类型(如 int、string、float 等),可以使用适当的排序函数。
遍历:最后,我们遍历排序后的切片,并使用它来访问 map 中的值,按照特定顺序输出结果。
package main

import (
    "fmt"
    "sort"
)

func main() {
    // 创建一个 map
    fruits := map[string]int{
        "香蕉": 20,
        "苹果": 10,
        "橙子": 15,
        "草莓": 25,
    }

    // 1. 提取键到切片
    keys := make([]string, 0, len(fruits))
    for key := range fruits {
        keys = append(keys, key)
    }

    // 2. 对切片进行排序
    sort.Strings(keys)//调用sort.Strings()函数对切片进行排序,默认升序排序

    // 3. 按照排序后的键顺序遍历 map
    for _, key := range keys {
        fmt.Printf("%s 的数量是: %d\n", key, fruits[key])
    }
}

苹果 的数量是: 10
香蕉 的数量是: 20
草莓 的数量是: 25
橙子 的数量是: 15

元素为map类型的切片

在 Go 语言中,切片的元素可以是任意类型,包括 map 类型。这种灵活性使得可以轻松地组织和管理复杂的数据结构。

当需要存储一组具有相同特征的物体时,可以使用 map 来表示每个物体的属性,然后将这些 map 存储在切片中。

逻辑步骤概述
创建切片:我们定义了一个切片 studentGrades,其元素类型为 map[string]int,用来存储学生的姓名和成绩。

添加元素:使用 append() 函数将新的 map 添加到切片中,每个 map 存储一个学生的成绩。

遍历切片:通过两层 for 循环,遍历切片中的每个 map,并输出学生的姓名和成绩。
package main

import "fmt"

func main() {
    // 创建一个切片,元素类型为 map[string]int
    var studentGrades []map[string]int

    // 添加学生的成绩到切片中
    studentGrades = append(studentGrades, map[string]int{
        "Alice": 85,
        "Bob":   90,
    })

    studentGrades = append(studentGrades, map[string]int{
        "Charlie": 70,
        "David":   95,
    })

    // 遍历切片中的 map
    for i, grades := range studentGrades {
        fmt.Printf("学生 %d 的成绩:\n", i+1)
        for name, grade := range grades {
            fmt.Printf("  %s: %d\n", name, grade)
        }
    }
}

学生 1 的成绩:
  Alice: 85
  Bob: 90
学生 2 的成绩:
  Charlie: 70
  David: 95

 
可以根据需要使用不同类型的 map,例如:

map[string]string:用于存储键为字符串,值也为字符串的映射关系,如存储用户信息或配置项。

嵌套结构:可以将更复杂的结构体作为值。

type Student struct {
    Name  string
    Grade int
}

    var students []map[string]Student
    
    students = append(students, map[string]Student{
        "Alice": {Name: "Alice", Grade: 85},
        "Bob":   {Name: "Bob", Grade: 90},
    })
   

值为切片类型的map

在 Go 语言中,您可以创建一个 map,其值类型为切片(slice)。

这种数据结构非常适用于需要将多个值与某个键关联的场景,

在 Go 语言中,使用值为切片类型的 map 允许您轻松管理多个值与对应键的关系。

这种灵活性使得可以满足复杂的数据结构需求,便捷地组织和访问数据。

在实际开发中,可用于各种场景,例如学生管理、产品信息处理等。

比如将多个学生的成绩与他们的名字关联,或是将多个订单的项目与一个用户关联。


逻辑步骤概述
创建 map:初始化一个 map,键为 string,值为 []int(整型切片)。

添加数据:向 map 中添加键值对,其中值是一个切片,存储多个成绩。

遍历 map:通过 for range 遍历 map,并对每个键值的切片进行进一步遍历,输出学生的姓名及其成绩。

更新成绩:使用 append() 函数向某个学生的成绩切片中添加新的成绩,实现动态更新。
package main

import "fmt"

func main() {
    // 创建一个 map,键为字符串,值为整数切片
    scores := make(map[string][]int)

    // 向 map 中添加数据
    scores["Alice"] = []int{85, 90, 92}
    scores["Bob"] = []int{78, 82}
    scores["Charlie"] = []int{88, 91, 85}

    // 遍历 map
    for name, scoreList := range scores {
        fmt.Printf("%s 的成绩: ", name)
        for _, score := range scoreList {
            fmt.Printf("%d ", score)
        }
        fmt.Println() // 换行
    }

    // 示例:给某个学生添加一个成绩
    scores["Alice"] = append(scores["Alice"], 95)
    fmt.Printf("更新后 Alice 的成绩: %v\n", scores["Alice"])
}
Alice 的成绩: 85 90 92 
Bob 的成绩: 78 82 
Charlie 的成绩: 88 91 85 
更新后 Alice 的成绩: [85 90 92 95]

其他用法

可以根据需要将值的类型定义为其他类型的切片,

如 map[string][]string 用于存储学生的课程列表,

或 map[string][]float64 用于存储某个产品的价格历史等。

courses := make(map[string][]string)
courses["Alice"] = []string{"数学", "英语", "科学"}
courses["Bob"] = []string{"艺术", "体育"}

for student, courseList := range courses {
    fmt.Printf("%s 的课程: %v\n", student, courseList)
}

prices := make(map[string][]float64)
prices["苹果"] = []float64{1.5, 2.0, 2.5}
prices["香蕉"] = []float64{1.8, 2.2, 2.6}

for fruit, priceList := range prices {
    fmt.Printf("%s 的价格: ", fruit)
    for _, price := range priceList {
        fmt.Printf("%.2f ", price)
    }
    fmt.Println() // 换行
}


注意事项

无序性:map 的元素顺序是无序的。在遍历时,输出的顺序并不一定与插入的顺序相同。

引用类型:map 是引用类型,传递给函数时传递的是引用,因此对 map 的修改会影响到原始数据。

零值:如果尝试访问一个不存在的键,Go 会返回该值类型的零值。在整型 map 中,未存在的键返回 0。

并发安全:map 在并发环境下不安全,多个 goroutine 同时读写 map 可能会导致程序崩溃。可使用 sync.Mutex 或其他机制来实现并发安全访问。


原文地址:https://blog.csdn.net/gopher9511/article/details/142407841

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!