• [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的代价太大了。

  • 相关阅读:
    [导入]在.NET下如何实现密码Hash化
    [导入]强大的.NET反编译工具Reflector及插件
    [导入]XML数据岛(XML Data Island)
    验证视图状态 MAC 失败。处理办法
    ASP.NET格式化字符串
    .NET 开发框架技术资料搜集
    网页中图片大小自动调整三种方法
    用户 'azhk' 登录失败。原因: 未与信任 SQL Server 连接相关联。
    jstl及el表达式笔记
    杰普Core Java课程笔记1
  • 原文地址:https://www.cnblogs.com/liujunhang/p/12534609.html
Copyright © 2020-2023  润新知