首页 >后端开发 >Golang >Go 中的地图

Go 中的地图

PHPz
PHPz原创
2024-07-23 00:48:33465浏览

Maps in Go

简介

Go 合并了一个本地类型,它实现了称为 map 的哈希表。它是一种数据类型,由唯一键的集合和每个键的值的集合组成。
例如,它可以与其他语言中的字典进行比较,字典存储键值对。这些值是使用键访问的,就像我们在上一篇文章中看到的数组和切片一样。
索引不限于数组或切片中的数字,并且元素没有排序,因此如果我们打印地图,如果我们不执行任何操作来覆盖其打印并强制执行所需的顺序,它将返回随机顺序。

地图声明和初始化

要声明一个映射,它是用map[key]value完成的,其中key将是我们想要的键的类型(它必须是可比较的类型https://go.dev/ref/spec#Comparison_operators ) 和 value 将是我们希望映射存储在每个键中的类型,无论它是什么类型,从 int 到结构,或另一个映射,无论我们想要什么。

与切片一样,映射是引用类型,这意味着映射的零值将为 nil。
发生这种情况是因为它下面有一个存储键和值的哈希表,它们只是它们的一个信封和抽象。

如果我们将其声明为:

var m map[int]int

其价值将为零。

如果我们希望它的值为零,我们可以使用声明:

m := map[int]int{}

我们甚至可以使用 make 函数像切片一样初始化它。

m := make(map[string]string)

这样做将使用适当的内存池初始化哈希映射,从而返回指向该数据结构的映射。

从地图中添加和读取值

向映射添加值是通过使用大括号 [] 和大括号来完成的,就像数组或切片一样。在此示例中,我们将创建一个键为字符串、值为整数的映射,用于存储姓名和年龄。

ages := make(map[string]int)

ages["John"] = 33
ages["Charly"] = 27
ages["Jenny"] = 45
ages["Lisa"] = 19

如果我们想在声明映射时向其中添加值,我们可以使用简短声明并在同一步骤中完成所有操作:

ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19}

要读取值,我们只需指示映射的键,它将返回该值。例如,要找出丽莎的年龄,我们可以这样做:

fmt.Println(ages["Lisa"]) // 19

如果我们尝试访问不存在的键,则获得的值将是该类型的零值,在本例中它将是“”,因为它是一个字符串。

为了检查映射中是否存在某个元素,我们可以检查该类型是否为默认类型,但这不是很可靠,因为它可能存在,但其值为空字符串,或者在 int 的情况下为 0 ,这将与其零值匹配,因此 Go 可以帮助我们完成以下操作:

val, ok := ages["Randy"]

如果我们将映射等于两个值,第一个将是通过键访问的该元素的值,在本例中为“Randy”,它不存在,第二个将是一个布尔值,它将指示它是否存在存在与否。

如果我们对值不感兴趣,只是想检查某个键是否存在,我们可以使用 _ 来忽略该值,如下所示:

_, ok := ages["Randy"]

与数组和切片一样,我们可以使用 len 函数来找出映射中有多少个元素。

fmt.Println(len(ages)) // 4

如果我们想要修改一个值,就像使用一个键访问该值并将其与另一个值匹配一样简单,它就会被修改。

如果我们声明第二个映射指向第一个映射,如果我们修改第二个映射的值,因为它是引用类型,我们将修改第一个映射的值,因为两者在下面共享相同的哈希表。

ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19}
agesNew := ages
agesNew["Bryan"] = 77
fmt.Println(agesNew) // map[Bryan:77 Charly:27 Jenny:45 John:33 Lisa:19]
fmt.Println(ages) // map[Bryan:77 Charly:27 Jenny:45 John:33 Lisa:19]

从地图中删除值

为了从映射中删除元素,Go 为我们提供了一个删除函数,其签名如下:delete(m map[Type]Type1, key Type),它接收一个映射和要删除的键。
在前面的例子中,如果我们想消除“Lisa”,我们会这样做:

delete(ages, "Lisa")

循环遍历地图

如果我们想要浏览地图的内容,我们可以使用 for 和我们在数组和切片的帖子中已经看到的范围变化来完成。
这样,第一个元素将是索引,因此是键,第二个元素是值。

for key, value := range ages {
    fmt.Printf("%s: %d\n", key, value)
}

// Output:
// Jenny: 45
// Lisa: 19
// John: 33
// Charly: 27

与数组和切片一样,如果我们只对值感兴趣,而不对键感兴趣,我们可以使用_省略它。

for _, value := range ages {
    fmt.Println(value)
}

// Output:
// 19
// 33
// 27
// 45

如果我们感兴趣的只是键,我们可以将范围分配给单个变量来获取它:

for key := range ages {
    fmt.Println(key)
}

// Output:
// John
// Charly
// Jenny
// Lisa

Sort a map

As I mentioned in the introduction, in a map the information is not ordered, so when looping through it we cannot specify what order it follows, nor can Go guarantee that the order between executions is the same.
As we saw with arrays and slices, in the standard library there is a sort package which helps us sort elements: https://pkg.go.dev/sort

Following our example with ages and using sort, we can sort the keys of the map before traversing it and thus guarantee that it will be accessed in order.

ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19}
keys := make([]string, 0, len(ages))
for k := range ages {
    keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
    fmt.Println(k, ages[k])
}

// Output:
// Charly 27
// Jenny 45
// John 33
// Lisa 19

We declare our ages map with the short declaration as we saw before.
We create a string slices to store the keys and use the make method with 0 length, since we do not have any keys at the moment, but we do reserve the capacity it will have using the len method for the length of our map.
We go through the ages map to keep its keys and add them to the created slice.
We sort the keys alphabetically with the sort.Strings function.
We go through the slice of keys, already ordered, and access the map with the key in question.
This way we will access the map in an orderly manner and we can do the logic that our program needs.

Problems with Concurrency

Something to keep in mind with maps is that they are not safe to use concurrently. If these are concurrent reads, either accessing a value or through a for with a range, there is no problem with multiple goroutines accessing it at the same time.
The problematic case is when you want to update the value of a map concurrently, either by adding or removing elements from it, and at the same time you are reading it from another side, for example.
To solve this situation there are several possible solutions, which I will not go into much detail, I will simply mention and leave it to your choice to delve deeper into them.

If we use the sync package: https://pkg.go.dev/sync from the standard library, we can control the synchrony between the different goroutines.
A possible use is the RWMutex type which allows us to lock and unlock reads and writes to a type. So if we have a type that contains a sync.RWMutex and a map we can control when it can be accessed.
Another interesting type to investigate within the same sync package is Map, which already offers us a series of functions that will help us work with our map, which in the end we will not be able to work with natively, as with the previous solution.
Depending on the use case we are implementing, one or the other will be more useful to us, and there is no one better than the other, it will always depend on what we need.

I hope everything that I have tried to explain in this post has been clear, and please if there is any part that has not been completely clear or there are parts that I have not covered that you would like me to do, leave me a comment right here or through my social networks that you have on my profile and I will be happy to respond.

以上是Go 中的地图的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn