Go语言实践[回顾]教程20--详解Go语言复合数据类型之映射 map

Go语言实践[回顾]教程20--详解Go语言复合数据类型之映射 map

  • 映射 map 的概念
  • map 的声明及初始化
  • map 的长度与容量
  • map 元素的获取与遍历
  • map 元素的增删改查

  我们前面了解过数组,知道数组的元素是通过位置号(索引号)有序排列的,获取元素也是通过索引获取的。而 map 类型与数组相似,也是由元素集合而成。但是 map 的元素不是有序排列的,也没有位置号(索引号),取而代之的是 键名(索引名),也就是要给每一个元素取一个唯一的名字,然后对应一个元素值。这样一个完整元素就是一个键值对形式,这与 PHP 的键值对数组、Python 的字典结构一样。

映射 map 的概念

  map 是以键值对形式为元素结构形成的无序集合,且是引用类型。元素的定位通过键名进行的,键名在集合内不可重复,必须唯一。如同日常生活中的对照表或者字典,用一个名字对应一个值,所以称为映射也很形象。map 的元素是可以动态增加的,未初始化的 map 的整体值为 nil。

map 的声明及初始化

  map 声明的语法主体格式:map [ 键名类型 ] 值类型
  ● 键名类型:元素名的数据类型。键名可以是 string、int 等任何可以比较的类型(数组、切片、结构体等是不能作为键名的),通常使用 string 类型居多。
  ● 值类型:元素值的数据类型,可以是任意类型。

// test01 项目的 main 包,文件名 main.go
package main

import (
	"fmt"
)

// 主函数,程序入口
func main() {
	var m1 map[string]int                          // 完整格式声明键名是 string 类型、值是 int 类型的 map
	var m2 = map[string]int{"one": 11, "two": 22}  // 完整格式声明并初始化两个键值对元素 "one": 11, "two": 22
	m3 := map[string]int{"one": 11, "two": 22}     // 简式声明并初始化两个键值对元素 "one": 11, "two": 22
	m4 := make(map[string]int)                     // 使用 make 函数简式声明键名是 string 类型、值是 int 类型的 map
	m5 := make(map[string]int, 8)                  // 使用 make 函数简式声明并预设容量为 8 个元素

	fmt.Println("映射 m1:", m1)
	fmt.Println("映射 m2:", m2)
	fmt.Println("映射 m3:", m3)
	fmt.Println("映射 m4:", m4)
	fmt.Println("映射 m5:", m5)
	fmt.Println(m1 == nil, m4 == nil)
}

  上述代码编译运行结果如下:

映射 m1: map[]
映射 m2: map[one:11 two:22]
映射 m3: map[one:11 two:22]
映射 m4: map[]
映射 m5: map[]
true false

  使用 make 函数声明 map 与不使用 make 函数直接声明的区别是,make 函数会直接创建并初始化内存区块,并可以预留键值对元素数量的空间,避免动态增加时频繁触发内存分配而影响性能。在上述代码最后一个打印输出语句就可以看出明显的区别了。

map 的长度与容量

  map 是引用类型,会动态增加键值对元素数,也有长度和容量的概念,cap 函数不能用于获取 map 的容量,但 len 函数是可以获取 map 的键值对元素个数的。通过示例理解一下:

// test01 项目的 main 包,文件名 main.go
package main

import (
	"fmt"
)

// 主函数,程序入口
func main() {
	m1 := map[string]int{"one": 11, "two": 22}
	m2 := make(map[string]int, 8)

	fmt.Println("映射 m1长度:", len(m1))
	fmt.Println("映射 m2长度:", len(m2))
}

  上述代码编译运行结果如下:

映射 m1长度: 2
映射 m2长度: 0

  虽然不能直接获取 map 的容量看到直观的效果,但根据 切片 章节关于长度和容量的分析,我们可以很快理解 make 函数第2个参数的意义。

map 元素的获取与遍历

  其实 map 元素的获取与数组和切片一样,都是方括号里加上索引,只不过 map 的索引不是位置号了,是自定义的键名。而遍历也是如此,数组和切片的遍历方法同样适用于 map,只是注意索引是键名的转变。

// test01 项目的 main 包,文件名 main.go
package main

import (
	"fmt"
)

// 主函数,程序入口
func main() {
	m := map[string]int{"one": 11, "two": 22, "three": 33}

	fmt.Println("修改前输出映射 m 中 one 的值:", m["one"])

	m["one"] = 20

	fmt.Println("修改后输出映射 m 中 one 的值:", m["one"])

	for k, v := range m {
		fmt.Println("遍历输出", k, "->", v)
	}
}

  上述代码编译运行结果如下:

修改前输出映射 m 中 one 的值: 11
修改后输出映射 m 中 one 的值: 20
遍历输出 two -> 22
遍历输出 three -> 33
遍历输出 one -> 20

  从输出结果看,可以充分证明 map 是无序的,多次编译运行,发现输出顺序会变化不同。其它方面与切片和数组一样,就不过多描述了。有不熟悉的可以看前面的章节。

map 元素的增删改查

  直接看示例代码:

// test01 项目的 main 包,文件名 main.go
package main

import (
	"fmt"
)

// 主函数,程序入口
func main() {
	m := map[string]int{"one": 11, "two": 22}

	fmt.Println("声明与初始化后的 m:", m)

	// 增加或修改,如果 m 中存在 three 键名,则直接修改,否则增加键 three,值为 56
	m["three"] = 56

	fmt.Println("增加 three 后的 m:", m)

	// 删除键名为 one 的键值对元素
	delete(m, "one")

	fmt.Println("删除 one 后的 m:", m)

	// 查找 m 中键名为 two 的元素,获取其值
	a := m["two"]

	fmt.Println("变量 a 的值:", a)
}

  上述代码编译运行结果如下:

声明与初始化后的 m: map[one:11 two:22]
增加 three 后的 m: map[one:11 three:56 two:22]
删除 one 后的 m: map[three:56 two:22]
变量 a 的值: 22

  删除有专用函数 delete(要操作的map, 要删除元素的键名),如果想删除所有元素(清空),那就不要删除,重新 make 一个 map 更高效,垃圾回收会帮你收拾残局。而增加与修改的赋值语句是一模一样的,是增加还是修改,取决于给定的键名在指定 map 中是否存在,如果存在就修改它,不存在就增加它。

  特别注意

  ● 如果 map 的某个值是引用类型,那不要通过 map 直接修改该引用类型中的数据,应该先修改引用的数据,然后再将整体数据赋值给 map 的这个键。后面会有章节在示例中描述。

  ● map 不是并发安全的,就是并发使用 map 类型数据时,在并发环境下,只读可以,同时读写会有问题,这种情况下需要使用 sync.Map 来替代,后面会又章节讲述它的应用。
.
.
上一节:Go/Golang语言学习实践[回顾]教程19–详解Go语言复合数据类型之切片 []

下一节:Go/Golang语言学习实践[回顾]教程21–详解Go语言的空值、零值、nil
.

你可能感兴趣的:(Go语言,golang,map,映射,Go语言教程,Go)