了解Go中的地图

大多数现代编程语言都具有字典或散列类型的概念。这些类型通常用于与映射到值的键成对存储数据。在Go中,地图是大多数程序员所认为的字典类型。它将键映射到值,使键值对成为在Go中存储数据的有用方法。在本文中了解Go地图的工作原理。

大多数现代编程语言都具有字典散列类型的概念。 这些类型通常用于与映射到成对存储数据。

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

map[key]value{}

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

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

除了花括号外,整个地图中还有冒号连接键值对。 冒号左边的单词是键。 键可以是Go中的任何类似类型,如stringsints等。

示例映射中的键是:

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

冒号右边的单词是值。 值可以是任何数据类型。 示例映射中的值为:

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

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

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

这将是你的输出:

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

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

访问地图项目

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

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

fmt.Println(sammy["name"])

并获得输出值:

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()方法。 但是,它确实允许使用range运算符进行迭代:

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

通过Go中的地图进行测距时,它将返回两个值。 第一个值是键,第二个值是值。 Go将使用正确的数据类型创建这些变量。 在这种情况下,map键是一个string因此key也将是一个字符串。 value也是一个字符串:

"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)

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

输出将仅显示地图的键:

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

同样,密钥没有排序。 如果要对它们进行排序,可以使用sort包中的sort.Strings函数:

sort.Strings(keys)

使用此功能,您将收到以下输出:

["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)

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

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

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

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

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

4

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

检查存在

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

让我们在你知道不存在的地图中查找一个值并查看返回的值:

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

您将看到以下输出:

0

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

在许多情况下,这是不可取的,会导致程序中的错误。 在地图中查找值时,Go可以返回第二个可选值。 第二个值是bool ,如果找到密钥则为false如果未找到密钥则为false 在Go中,这被称为ok惯用语。 即使您可以将捕获第二个参数的变量命名为Go,但在Go中,您总是将其命名为ok

count, ok := counts["sammy"]

如果关键的sammy存在于counts图中,则ok将为true 否则ok会是假的。

您可以使用ok变量来决定程序中的操作:

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

这将导致以下输出:

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中的地图中检索值时,最好还是检查它的存在,以避免程序中出现错误。

修改地图

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

添加和更改地图项目

不使用方法或函数,可以向地图添加键值对。 您可以使用maps变量名称,后跟方括号[ ]的键值,并使用equal =运算符设置新值:

map[key] = value

实际上,您可以通过向名为usernames的地图添加键值对来查看此工作:

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

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

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

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

由于地图是无序返回的,因此该对可能出现在地图输出中的任何位置。 如果稍后在程序文件中使用usernames映射,则它将包含其他键值对。

您还可以使用此语法修改分配给键的值。 在这种情况下,您引用现有密钥并向其传递不同的值。

考虑一个名为followers的地图,跟踪给定网络上用户的关注者。 用户"drew"今天的粉丝有一个碰撞,所以你需要更新传递给"drew"键的整数值。 您将使用Println()函数检查地图是否已被修改:

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

您的输出将显示drew的更新值:

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块检查地图中是否存在名称并打印反馈。 如果名称存在,则继续返回循环的顶部。 如果名称不在地图中,则会向用户提供反馈,然后会为相关名称请求新的用户名。 程序再次检查是否有错误。 没有错误,它会修剪回车符,将用户名值分配给名称键,然后打印数据已更新的反馈。

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

go run usernames.go

您将看到以下输出:

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语法将项目添加到地图或修改值。

删除地图项目

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

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

delete(map, key)

让我们定义一个权限地图:

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

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

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

输出将确认删除:

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

delete(permissions, 16)permissions映射中删除键值对16:"modify"

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

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

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

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

map[]

由于映射是可变数据类型,因此可以添加,修改它们,并删除和清除项目。

结论

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