跳转到主要内容

大多数现代编程语言都有字典或哈希类型的概念。这些类型通常用于将数据与映射到值的键成对存储。

在 Go 中,map 数据类型是大多数程序员认为的字典类型。它将键映射到值,生成键值对,这是在 Go 中存储数据的有用方式。映射是通过使用关键字 map 后跟方括号 [ ] 中的键数据类型,然后是值数据类型来构造的。然后将键值对放在 { } 两侧的花括号内:

map[key]value{}


您通常在 Go 中使用地图来保存相关数据,例如 ID 中包含的信息。带有数据的地图如下所示:

map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}


除了花括号之外,整个映射中还有冒号连接键值对。冒号左边的词是键。键可以是 Go 中任何可比较的类型,例如字符串、整数等。

示例地图中的键是:

  • "name"
  • "animal"
  • "color"
  • "location"


冒号右边的词是值。值可以是任何数据类型。示例地图中的值是:

  • "Sammy"
  • "shark"
  • "blue"
  • "ocean"


与其他数据类型一样,您可以将地图存储在变量中,然后将其打印出来:

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(sammy)


这将是您的输出:




Output

map[animal:shark color:blue location:ocean name:Sammy]


键值对的顺序可能已经改变。在 Go 中,地图数据类型是无序的。无论顺序如何,键值对都将保持不变,使您能够根据它们的关系含义访问数据。

访问Map项


您可以通过引用相关键来调用映射的值。 由于地图提供了用于存储数据的键值对,因此它们可能是 Go 程序中重要且有用的项目。

如果你想隔离 Sammy 的用户名,你可以通过调用 sammy["name"]; 保存地图和相关键的变量。 让我们打印出来:

fmt.Println(sammy["name"])


并接收该值作为输出:




Output

Sammy


地图的行为类似于数据库; 不像使用切片那样调用整数来获取特定的索引值,而是将值分配给键并调用该键以获取其相关值。

通过调用键“name”,您会收到该键的值,即“Sammy”。

同样,您可以使用相同的格式调用 sammy 映射中的剩余值:

fmt.Println(sammy["animal"])
// returns shark

fmt.Println(sammy["color"])
// returns blue

fmt.Println(sammy["location"])
// returns ocean

通过使用映射数据类型中的键值对,您可以引用键来检索值。

键和值


与某些编程语言不同,Go 没有任何方便的函数来列出映射的键或值。一个例子是 Python 的 .keys() 方法用于字典。但是,它确实允许使用范围运算符进行迭代:

for key, value := range sammy {
    fmt.Printf("%q is the key for the value %q\n", key, value)
}


在 Go 中遍历地图时,它将返回两个值。第一个值是键,第二个值是值。 Go 将使用正确的数据类型创建这些变量。在这种情况下,映射键是一个字符串,所以键也将是一个字符串。该值也是一个字符串:

Output
"animal" is the key for the value "shark"
"color" is the key for the value "blue"
"location" is the key for the value "ocean"
"name" is the key for the value "Sammy"


要获取仅包含键的列表,您可以再次使用范围运算符。您可以仅声明一个变量以仅访问键:

keys := []string{}

for key := range sammy {
    keys = append(keys, key)
}
fmt.Printf("%q", keys)

该程序首先声明一个切片来存储您的密钥。

输出将仅显示地图的键:




Output

["color" "location" "name" "animal"]


同样,键未排序。如果要对它们进行排序,请使用 sort 包中的 sort.Strings 函数:

sort.Strings(keys)


使用此函数,您将收到以下输出:




Output

["animal" "color" "location" "name"]


您可以使用相同的模式仅检索地图中的值。在下一个示例中,您预先分配切片以避免分配,从而使程序更高效:

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}

items := make([]string, len(sammy))

var i int

for _, v := range sammy {
    items[i] = v
    i++
}
fmt.Printf("%q", items)

首先,您声明一个切片来存储您的密钥;由于您知道需要多少项目,因此可以通过将切片定义为完全相同的大小来避免潜在的内存分配。然后你声明你的索引变量。由于您不需要密钥,因此在开始循环时使用 _ 运算符来忽略密钥的值。您的输出将如下所示:




Output

["ocean" "Sammy" "shark" "blue"]


要确定地图中的项目数,可以使用内置的 len 函数:

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(len(sammy))


输出显示地图中的项目数:




Output

4


尽管 Go 没有提供方便的函数来获取键和值,但它只需要几行代码就可以在需要时检索键和值。

检查存在


当请求的键缺失时,Go 中的 Maps 将返回 map 的值类型的零值。因此,您需要一种替代方法来区分存储的零和丢失的密钥。

让我们在您知道不存在的映射中查找一个值并查看返回的值:

counts := map[string]int{}
fmt.Println(counts["sammy"])


您将看到以下输出:




Output

0


即使键 sammy 不在 map 中,Go 仍然返回值 0。这是因为 value 数据类型是 int,并且因为 Go 对所有变量都有零值,所以它返回零值 0。

在许多情况下,这是不可取的,并且会导致程序中出现错误。在地图中查找值时,Go 可以返回第二个可选值。第二个值是一个布尔值,如果找到密钥,则为 true,如果未找到密钥,则为 false。在 Go 中,这被称为 ok 成语。即使您可以将捕获第二个参数的变量命名为任何您想要的名称,但在 Go 中,您始终可以将其命名为:

count, ok := counts["sammy"]


如果键 sammy 存在于 counts 映射中,那么 ok 将是真的。否则 ok 将是错误的。

你可以使用 ok 变量来决定在你的程序中做什么:

if ok {
    fmt.Printf("Sammy has a count of %d\n", count)
} else {
    fmt.Println("Sammy was not found")
}


这将导致以下输出:




Output

Sammy was not found


在 Go 中,您可以将变量声明和条件检查与 if/else 块结合起来。这允许您使用单个语句进行此检查:

if count, ok := counts["sammy"]; ok {
    fmt.Printf("Sammy has a count of %d\n", count)
} else {
    fmt.Println("Sammy was not found")
}


在 Go 中从映射中检索值时,最好检查它的存在以及避免程序中的错误。

修改Map


地图是一种可变数据结构,因此您可以修改它们。让我们看看本节中添加和删除地图项。

添加和更改Map项


在不使用方法或函数的情况下,您可以将键值对添加到映射中。您可以使用 maps 变量名称,后跟方括号 [ ] 中的键值,并使用等于 = 运算符设置新值:

map[key] = value


在实践中,您可以通过将键值对添加到名为 usernames 的映射中来查看这项工作:

usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}

usernames["Drew"] = "squidly"
fmt.Println(usernames)

输出将在地图中显示新的 Drew:squidly 键值对:




Output

map[Drew:squidly Jamie:mantisshrimp54 Sammy:sammy-shark]


因为地图是无序返回的,所以这对可能出现在地图输出的任何地方。如果您稍后在程序文件中使用用户名映射,它将包含附加的键值对。

您还可以使用此语法来修改分配给键的值。在这种情况下,您引用一个现有的键并将不同的值传递给它。

考虑一个名为追随者的地图,它跟踪给定网络上用户的追随者。用户“drew”今天的追随者数量激增,因此您需要更新传递给“drew”键的整数值。您将使用 Println() 函数检查地图是否已修改:

followers := map[string]int{"drew": 305, "mary": 428, "cindy": 918}
followers["drew"] = 342
fmt.Println(followers)


您的输出将显示绘制的更新值:




Output

map[cindy:918 drew:342 mary:428]


您会看到关注者的数量从整数值 305 跃升至 342。

您可以使用此方法将键值对添加到具有用户输入的映射中。让我们编写一个名为 usernames.go 的快速程序,它在命令行上运行并允许用户输入以添加更多名称和关联的用户名:

usernames.go

package main

import (
    "fmt"
    "strings"
)

func main() {
    usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}

    for {
        fmt.Println("Enter a name:")

        var name string
        _, err := fmt.Scanln(&name)

        if err != nil {
            panic(err)
        }

        name = strings.TrimSpace(name)

        if u, ok := usernames[name]; ok {
            fmt.Printf("%q is the username of %q\n", u, name)
            continue
        }

        fmt.Printf("I don't have %v's username, what is it?\n", name)

        var username string
        _, err = fmt.Scanln(&username)

        if err != nil {
            panic(err)
        }

        username = strings.TrimSpace(username)

        usernames[name] = username

        fmt.Println("Data updated.")
    }
}

在 usernames.go 中,您首先定义原始地图。然后,您设置一个循环来迭代名称。您要求您的用户输入一个名称并声明一个变量来存储它。接下来,您检查是否有错误;如果是这样,程序将退出恐慌。因为 Scanln 捕获整个输入,包括回车,您需要从输入中删除任何空格;您可以使用 strings.TrimSpace 函数执行此操作。

if 块检查名称是否存在于地图中并打印反馈。如果名称存在,则继续返回循环的顶部。如果名称不在地图中,它会向用户提供反馈,然后会要求为相关名称提供新用户名。程序再次检查是否有错误。没有错误,它会删除回车,将用户名值分配给 name 键,然后打印数据已更新的反馈。

让我们在命令行上运行程序:

go run usernames.go


您将看到以下输出:

Output
Enter a name:
Sammy
"sammy-shark" is the username of "Sammy"
Enter a name:
Jesse
I don't have Jesse's username, what is it?
JOctopus
Data updated.
Enter a name:


完成测试后,按 CTRL + C 退出程序。

这显示了如何以交互方式修改地图。使用这个特定的程序,一旦您使用 CTRL + C 退出程序,您将丢失所有数据,除非您实现了一种处理读取和写入文件的方法。

总而言之,您可以使用 map[key] = value 语法将项目添加到地图或修改值。

删除Map项


正如您可以在地图数据类型中添加键值对和更改值一样,您也可以在地图中删除项目。

要从映射中删除键值对,可以使用内置函数 delete()。第一个参数是您要从中删除的地图。第二个参数是您要删除的键:

delete(map, key)


让我们定义一个权限映射:

permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16:"modify"}


您不再需要修改权限,因此您将从地图中删除它。然后您将打印出地图以确认它已被删除:

permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16: "modify"}
delete(permissions, 16)
fmt.Println(permissions)


输出将确认删除:




Output

map[1:read 2:write 4:delete 8:create]


行 delete(permissions, 16) 从权限映射中删除键值对 16:"modify"。

如果要清除所有值的映射,可以将其设置为相同类型的空映射。这将创建一个新的空映射来使用,旧映射将被垃圾收集器从内存中清除。

让我们删除权限映射中的所有项目:

permissions = map[int]string{}
fmt.Println(permissions)


输出显示您现在有一个没有键值对的空映射:




Output

map[]


因为地图是可变数据类型,所以它们可以被添加、修改、删除和清除项目。

结论


本教程探讨了 Go 中的地图数据结构。映射由键值对组成,提供了一种不依赖索引来存储数据的方法。这允许我们根据值的含义和与其他数据类型的关系来检索值。

文章链接