[golang]语法基础之map

说明

在go语言当中,集合类型除了数组、切片以外,还有一种就是Map。

Map是一种数据结构,是一个集合,主要用来存储一系列无序的键值对。Map主要基于键来进行存储,键就像切片里面的索引一样,通过键可以快速的检索数据,一般来说键指向与该键相关联的值并且键值唯一。

内部实现

从本质来讲,Map是基于散列表来实现的(Hash表),所以迭代Map的时候,打印的Key和Value都是处于无序的状态,每一次迭代的结果都是不相同的。

映射的散列表包含一组桶。在存储、删除或者查找键值对的时候,所有操作都要先选择一个桶。把操作映射时指定的键传给映射的散列函数,就能选中对应的桶。这个散列函数的目的是生成一个索引,这个索引最终将键值对分布到所有可用的桶里。

这样的好处在于随着映射存储的增加,索引分布越均匀,访问键值对的速度就越快。

如果想要了解更多的存储细节,可以参考Hash相关的内容,在这我们只需要知道并且记住Map存储的是无序额键值对的集合。

声明和初始化

Map的创建可以通过make函数或者Map字面量。make函数在之前我们用它创建过切片,除了切片,还可以用来创建Map。

dict := make(map[string]int)

在上面的代码中创建了一个键类型为string,值类型为int的Map。创建好之后,这个map是空的,什么都没有,我们可以在其中存入一个键值对。

dict := make(map[string]int)

dict["李四"] = 30 

在上面的代码中存储了一个key为李四,value值为30的键值对数据。

除了使用make以外,还可以使用map字面量的方式创建和初始化map。我们下面采用字面量的形式来实现和上面相同的功能。

dict := map[string]int{"李四":30}

在上面的代码中,通过一个大括号多map进行了初始化。键值对通过:分开,如果想要同时初始化多个键值对,需要使用逗号进行分割。

如下:

dict := map[string]int{"张三":30,"李四":20}

如果你在使用map字面量创建的时候不希望指定任何键值对,那么也是可以的。

dict := map[string]int{}

通过上面的代码我们创建了一个空的map。

我们该如何创建一个nil的Map呢?

nil的Map是未初始化的,所以我们可以只声明一个变量,既不能使用map字面量,也不能使用make函数分配内存。

var dict map[string]int

上面的代码我们创建了一个nil的map,但是这样的map我们是不能够操作存储键值对的,必须要初始化后才可以,比如使用make函数,为其开启一块用于存储数据的内存,也就是所谓的初始化。

var dict map[string]int
dict = make(map[string]int)
dict["张三"] = 43
fmt.Println(dict)

Map的键可以是任何值,键的类型可以是内置的类型,也可以是结构类型,但是不管怎么样,这个键可以使用==运算符进行比较,所以像切片、函数以及含有切片的结构类型就不能用于Map的键了,因为他们具有引用的语义,不可比较。

对于Map的值来说,就没有什么限制了,切片这种在键里不能用的,完全可以用在值里。

使用Map

Map的使用和数组切片类似,在数组切片中是使用索引,而在Map中是通过键。

例如:

dict := make(map[string]int)
dict["张三"] = 30 

在上面的代码中,如果张三这个key存在,则会对其值进行修改,如果不存在,则新增这个键值对。

如果想要获取一个Map的值也很简单,类似于存储,使用key就可以获取。

age := dict["张三"]

在Go Map中,如果我们获取一个不存在的键的值,也是可以的,返回的是值类型的零值,这样就会导致我们不知道是真的存在一个为零值的键值对呢,还是说这个键值对就不存在。对此,Map为我们提供了检测一个键值对是否存在的方法。

age,exists := dict["李四"]

在上面的代码中,存在两个返回值,第一个返回值是键对应的值;第二个返回值标记这个键是否存在,这是一个bool类型的变量。我们可以通过这个布尔值判断该键是否存在。

如果我们需要删除一个Map中的键值对,可以使用go当中内置的delete函数。

delete(dict,"李四")

delete函数接受两个参数,第一个是要操作的Map,第二个是要删除的Map的键。

通过delete删除不存在的键也是可以的,只不过不会起到任何的作用。

想要遍历Map的话,可以使用for range循环,和遍历切片是一样的操作。

dict := map[string]int{"张三": 43}
for key, value := range dict {
    fmt.Println(key, value)
}

这里的range 返回两个值,第一个是Map的键,第二个是Map的键对应的值。这里再次强调,这种遍历是无序的,也就是键值对不会按既定的数据出现,如果想安顺序遍历,可以先对Map中的键排序,然后遍历排序好的键,把对应的值取出来,下面看个例子就明白了。

func main() {
    dict := map[string]int{"王五": 60, "张三": 43}
    var names []string
    for name := range dict {
        names = append(names, name)
    }
    sort.Strings(names) //排序
    for _, key := range names {
        fmt.Println(key, dict[key])
    }
}

这个例子里有个技巧,range 一个Map的时候,也可以使用一个返回值,这个默认的返回值就是Map的键。

在函数间传递Map

函数间传递Map是不会拷贝一个该Map的副本的,也就是说如果一个Map传递给一个函数,该函数对这个Map做了修改,那么这个Map的所有引用,都会感知到这个修改。

func main() {
    dict := map[string]int{"王五": 60, "张三": 43}
    modify(dict)
    fmt.Println(dict["张三"])
}

func modify(dict map[string]int) {
    dict["张三"] = 10
}

上面这个例子输出的结果是10,也就是说已经被函数给修改了,可以证明传递的并不是一个Map的副本。这个特性和切片是类似的,这样就会更高效,因为复制整个Map的代价太大了。

猜你喜欢

转载自www.cnblogs.com/liujunhang/p/12534609.html