Go基础-数组、切片、Map | 青训营笔记

141 阅读21分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第3篇笔记

image.png

1. 数组 (array)

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成,数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推,数组一档定义后,长度不能更改。

因为数组的长度是固定的,所以在Go语言中很少直接使用数组,一般都是使用切片来代替数组, 切片(slice)是可以增长和收缩的动态序列,功能也更灵活,要理解切片工作原理的话需要先理解数组。

1.1. 数组的声明

语法格式:var name [size]type

  • var:定义数组变量使用的关键字。
  • name:数组声明及使用时的变量名。
  • size:数组的元素数量(数组长度),可以是一个表达式,但是一定要在编译时就能获得确定值,不能含有到运行时才能确认大小的数值。
  • type:可以是任意基本类型,包括数组本身,类型为数组本身时,可以实现多维数组。

示例:

package main

import "fmt"

func main() {
	var stringarr [2]string
	stringarr[0] = "hello"
	stringarr[1] = "Golang"
	fmt.Println("stringarr = ", stringarr)
}
//stringarr =  [hello Golang]

1.2. 初始化数组

var arr = [3]int{1, 2, 3}
//或
arr := [3]int{1, 2, 3} 

如果数组长度不确定,可以使用...代替数组长度,编译器会自动根据元素个数确定数组的长度:

var arr = [...]int{1, 2, 3, 4, 5}
//或
arr := [...]int{1, 2, 3, 4, 5}

如果指定了数组长度,还可以对指定位置的元素进行初始化:

func main() {
	arr := [5]float64{0: 1.0, 4: 5.0}
	fmt.Println("arr = ", arr)
}
//arr =  [1 0 0 0 5]

1.3. 访问数组元素

使用下标索引的方式,即可对数组进行访问或者修改指定的数组元素的值:

func main() {
	arr := [2]string{"Hello", "Golang!"}
	fmt.Println("arr[0] = ", arr[0])
	fmt.Println("arr[1] = ", arr[1])
}
//arr[0] =  Hello
//arr[1] =  Golang!

1.4. 获取数组的长度

将数组作为参数传递给len()函数,可以获得数组的长度:

func main() {
	arr := [2]string{"Hello", "Golang!"}
	fmt.Println("arr[0] = ", arr[0])
	fmt.Println("arr[1] = ", arr[1])
	fmt.Println("数组长度为:", len(arr))
}
//arr[0] =  Hello
//arr[1] =  Golang!
//数组长度为: 2  

1.5. 数组比较

Go语言的数组比较,是使用 == 的方式,如果数组的元素个数不相同或者元素类型不相同,那么不能比较数组。

在两个数组类型相同(包括数组的长度,数组中元素的类型)的情况下,可以直接通过较运算符(==和!=)来判断两个数组是否相等,只有当两个数组的所有元素都是相等的时候数组才是相等的,不能比较两个类型不同的数组,否则程序将无法完成编译。

func main() {
	a := [2]int{1, 2}
	b := [...]int{1, 2}
	c := [3]int{1, 2, 3}
	fmt.Println(a == b) // true
	fmt.Println(a == c) // invalid operation: a == c (mismatched types [2]int and [3]int)
}

1.6. 多维数组

Go同样支持多维数组,声明多维数组的语法格式为:

var name [size1][size2]...[sizen]type

// 声明一个4行2列的数组
var array [4][2]int
// 声明并初始化一个4行2列的数组
array1 := [4][2]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}}
// 三维数组
array2 := [3][3]int{
    {0, 1, 2},
    {3, 4, 5},
    {6, 7, 8}
}

1.7. 数组是值类型

Go中的数组是值类型,而不是引用类型。当对数组进行传递时,传递的是原始数组的副本。比如:将数组a传递给变量b,通过变量b对数组进行更改,原数组并不受影响。

func main() {
	a := [3]int{1, 2, 3}
	b := a
	b[0] = 4
	b[1] = 5
	b[2] = 6
	fmt.Println(a == b)
	fmt.Println(a)
	fmt.Println(b)
}
//false
//[1 2 3]
//[4 5 6]

同样地,在把数组作为参数传递给函数时,依然是值传递,而原始数组不受影响。

2. 切片 (slice)

Go 数组的长度固定不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(动态数组),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用的传递机制。

切片数据结构包含 Go 语言需要操作底层数组的元数据,分别是指向底层数组的指针、切片访问的元素的个数len(即长度)和切片允许增长到的元素个数cap(即容量)。

2.1. 切片的声明

切片的声明有两种方式:

  1. 通过声明一个未指定大小的数组来定义切片:var name []type,只需要声明切片的类型而不需要声明长度。
  2. 使用make()函数创建切片:var name []type = make([]type, len),可以简写为name := make([]type, len),也可以为切片指定容量name := make([]type, len, capacity),len为切片的当前长度,capacity是可选参数,在不声明capacity的情况下,默认capacity = len。 注:使用 make() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。

2.2. 初始化切片

切片在未初始化时,默认为nil,长度为0,一般有如下两种形式对切片进行初始化:

  1. 声明且初始化切片

s := []int{1, 2, 3}[]表示是切片类型,初始值为1, 2, 3,其中capacity = len = 3

  1. 使用数组初始化切片

切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。从连续内存区域生成切片是常见的操作,格式如下: slice := arr[startIndex : endIndex],不包含结束位置的元素。

func main() {
	// 数组a
	a := [5]int{1, 2, 3, 4, 5}
    // 切片截取
	b := a[1:2]
	c := a[:5]
	d := a[1:]
	e := a[:]
	f := a[0:0]
	fmt.Println("a[1:2] = ", b)
	fmt.Println("a[ :5] = ", c)
	fmt.Println("a[1: ] = ", d)
	fmt.Println("a[ : ] = ", e)
	fmt.Println("a[0:0] = ", f)
}
//a[1:2] =  [2]
//a[ :5] =  [1 2 3 4 5]
//a[1: ] =  [2 3 4 5]  
//a[ : ] =  [1 2 3 4 5]
//a[0:0] =  []   

从数组或切片生成新的切片具有如下特点:

  • 取出的元素数量为:结束位置 - 开始位置,取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)]获取;
  • 当缺省开始位置时,表示从开头到结束位置;
  • 当缺省结束位置时,表示从开始位置到末尾;
  • 两者同时缺省时,与数组本身等效;
  • 两者同时为 0 时,等效于空切片,一般用于切片复位。

2.3. append()copy()

切片的长度是切片中元素的数量,切片的容量是切片最大能容纳的元素数量,可以通过len()函数获取切片的长度,通过cap()函数获取切片的容量。除此之外,还可以使用append()向切片追加一个或者多个元素,然后返回一个新的切片。copy()函数将原切片的元素复制到目标切片,并且返回复制的元素的个数。

append()函数会改变切片所引用的数组,从而影响到引用同一数组的其它切片。 当切片中没有剩余空间,即cap-len == 0时,此时将动态分配新的数组空间进行扩容,切片在扩容时,容量的扩展是按原切片容量的 2 倍数进行扩充,返回的切片指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的切片则不受影响。

package main

import "fmt"

func printSlice(x []int) {
	fmt.Printf("len = %d cap = %d slice = %v\n", len(x), cap(x), x)
}
func main() {
	var s []int
	printSlice(s)
	// 允许追加空切片
	s = append(s, 0)
	printSlice(s)

	// 向切片添加一个元素
	s = append(s, 1)
	printSlice(s)

	// 同时添加多个元素
	s = append(s, 2, 3, 4)
	printSlice(s)

	// 创建切片s1,是s容量的两倍
	s1 := make([]int, len(s), (cap(s))*2)
	// 拷贝s的内容到s1
	copy(s1, s)
	printSlice(s1)
}

注:copy()方法是进行值复制,切片s1与切片s两者不存在联系,切片s发生变化时,s1不会随着变化。

在上述的示例代码中,向切片添加元素都是添加到尾部,其实可以使用append()函数加上切片索引的形式实现在切片的任意位置插入元素。

package main

import "fmt"

func main() {
	var s = []string{"a", "c"}
	// 在index处添加一个元素
	s = append(s[:1], append([]string{"b"}, s[1:]...)...)
	fmt.Println(s)

	// 在头部插入元素
	s = append([]string{"0"}, s...)
	fmt.Println(s)

	// 在index处插入切片
	var newSlice = []string{" ", " ", " "}
	s = append(s[:1], append(newSlice, s[1:]...)...)
	fmt.Println(s)
}
//[a b c]
//[0 a b c]      
//[0       a b c]

在上述代码中,s[:1]返回的是切片的第一个元素,s[1:]返回的是从二个元开始的整个切片,切片截取搭配上append()函数可以实现非常灵活的操作。

注:在向可变参数的函数中传递切片时,需要在切片后面加上...

2.4. 删除切片元素

切片是一个引用类型(类似于一个指针),本身不保存数据,对切片做的任何修改都将反映到它所指向的底层数组。切片是引用类型,只能与 nil判定相等,不能互相判定相等。

切片和C语言指针类似,指针可以做运算偏移,但可能造成内存操作越界,切片在指针的基础上增加了大小,约束了切片对应的内存区域,切片使用中无法对切片内部的地址和大小进行手动调整,因此切片比指针更安全、强大。

func main() {
	a := [5]int{1, 2, 3, 4, 5}// 数组a
	var b []int // 声明一个切片b

	if (b == nil) {
		fmt.Println("切片为空...")
	}

	b = a[:]
	for i := range b {
		b[i]++
	}
	fmt.Println("a[1:2] = ", a)
}
//切片为空...
//a =  [2 3 4 5 6]

Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是在开头位置删除、在中间位置删除和在尾部删除,其中删除切片尾部的元素速度最快。

在开头位置删除

// 删除开头的元素可以直接移动数据指针
a = []int{1, 2, 3}
a = a[1:] // 删除开头1个元素
a = a[N:] // 删除开头N个元素
// 也可以不移动数据指针,但是将后面的数据向开头移动,可以用append原地完成
a = []int{1, 2, 3}
a = append(a[:0], a[1:]...) // 删除开头1个元素
a = append(a[:0], a[N:]...) // 删除开头N个元素
// 还可以用copy()函数来删除开头的元素
a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 删除开头1个元素
a = a[:copy(a, a[N:])] // 删除开头N个元素

在中间位置删除

// 删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用append或copy原地完成
a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素

在尾部位置删除

a = []int{1, 2, 3}
a = a[:len(a)-1] // 删除尾部1个元素
a = a[:len(a)-N] // 删除尾部N个元素

示例: 删除切片指定位置的元素。

package main
import "fmt"
func main() {
    seq := []string{"a", "b", "c", "d", "e"}
    // 指定删除位置
    index := 2
    // 查看删除位置之前的元素和之后的元素
    fmt.Println(seq[:index], seq[index+1:])
    // 将删除点前后的元素连接起来
    seq = append(seq[:index], seq[index+1:]...)
    fmt.Println(seq)
}
//[a b] [d e]
//[a b d e]

切片删除元素的操作过程:

Go语言中删除切片元素的本质是,以被删除元素为分界点,将前后两个部分的内存重新连接起来。

2.5. 切片扩容

在使用append()函数为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变,会在内存中创建一个新的底层数组,切片指向这个新的的底层数组,而不再指向原数组,所以如果发生扩容,对切片的更改并不会影响原数组。切片在扩容时,容量的扩展规律是按原容量的 2 倍数进行扩充。

package main

import "fmt"

func main() {
	var arr = [5]int{1, 2, 3, 4, 5}
	s1 := arr[:] // 指向arr数组
	s2 := arr[:] // 指向arr数组

	// 改变s1切片的元素,底层数组改变,s2切片也改变
	s1[1] = 0
	fmt.Println("arr = ", arr)
	fmt.Println("s2 = ", s2)

	// 默认len == cap
	fmt.Printf("长度:%d, 容量: %d\n", len(s1), cap(s1))
	// 向s1切片添加新元素,发生扩容
	s1 = append(s1, 6, 7, 8)
	fmt.Printf("长度:%d, 容量: %d\n", len(s1), cap(s1))
	fmt.Println("arr = ", arr)
	fmt.Println(s1)
}

注:当容量不足时,添加元素会进行扩容,扩容容量为原切片的两倍;扩容会创建一个新的数组,切片指向新数组,原数组并不会发生变化。

往一个切片中不断添加元素扩容的过程,类似于公司搬家,公司发展初期,资金紧张,人员很少,所以只需要很小的房间即可容纳所有的员工,随着业务的拓展和收入的增加就需要扩充工位,但是办公地的大小是固定的,无法改变,因此公司只能选择搬家,每次搬家就需要将所有的人员转移到新的办公点。

  • 员工和工位就是切片中的元素。
  • 办公地就是分配好的内存。
  • 搬家就是重新分配内存。
  • 无论搬多少次家,公司名称始终不会变,代表外部使用切片的变量名不会修改。
  • 由于搬家后地址发生变化,因此内存“地址”也会有修改。

3. Map

Map 是一种无序的键值对的集合,可以通过 key 来快速对应的 value 数据,key 可以是所有任何可以使用 == 进行比较的数据类型(基本类型),比如数字型、布尔型、字符串类型等,value 可以是任意的类型。可以使用for...range进行迭代,不过,Map 是无序的,我们无法决定它的返回顺序。Map同样是引用类型,长度不固定,可以通过len()函数返回键值对的数量。

3.1. map声明

Map的声明有两种方式,可以使用内建函数make()map关键字进行声明:

// 使用map关键字
var name map[keyType]valueType
// 使用make函数
name := make(map[keyType]valueType)

注:[keyType]valueType 之间允许有空格,未初始化的map的值为nil,不能直接使用和赋值。

var声明变量使用的关键字
namemap 变量的变量名
map声明 map 变量的关键字
keyTypemap 的键的类型
valueTypemap 的值的类型

3.2. 初始化map

  1. 声明同时初始化
package main

import "fmt"

func main() {
	// 声明+初始化
	cityMap := map[int]string{
		1: "广州",
		2: "上海",
		3: "北京",
		4: "杭州",
	}
	// 遍历map
	for key, value := range cityMap {
		fmt.Println(key, value)
	}
}

注:每次遍历map的返回的结果顺序都是不固定的。

  1. 先声明后初始化
package main

import "fmt"

func main() {
	// 声明一个map
	var cityMap map[string]string
	// 初始化map
	cityMap["广州"] = "小蛮腰"
	cityMap["上海"] = "东方明珠"
	cityMap["北京"] = "故宫"
	cityMap["杭州"] = "西湖"
	// 遍历map
	for key, value := range cityMap {
		fmt.Println(key, value)
	}
}
//运行时错误:panic: assignment to entry in nil map  

错误原因:map不同于array和基础类型,在声明时会初始化一个默认值,map是引用类型,如果未在声明时进行初始化,默认值是nil,不指向任何内存地址,所以nil map不能赋值,对nil map赋值会导致运行时错误。解决办法:可以在map声明后,通过make()函数为其分配内存地址后再进行赋值。

package main

import "fmt"

func main() {
	// 声明一个map,默认值为nil,不能直接赋值
	var cityMap map[string]string
	// 使用make函数为nil map分配内存
	cityMap = make(map[string]string)
	// 初始化map
	cityMap["广州"] = "小蛮腰"
	cityMap["上海"] = "东方明珠"
	cityMap["北京"] = "故宫"
	cityMap["杭州"] = "西湖"
	// 遍历map
	for key, value := range cityMap {
		fmt.Println(key, value)
	}
}

拓展:同为引用类型的slice,在使用append()nil slice添加元素并不会发生错误,原因在于append()函数底层的扩容机制(详情可参考2.5 切片扩容),append()函数将元素追加到切片的尾部时,如果数组太小无法进行追加,则会分配一个更大容量的数组,slice指向这个新数组。同理,将向nil slice追加元素时,会为nil slice重新分配新的数组,让nil slice指向这个数组的内存地址。nil map问题官方文档解释如下:This variable m is a map of string keys to int values:var m map[string]int,Map types are reference types, like pointers or slices, and so the value of m above is nil; it doesn't point to an initialized map. A nil map behaves like an empty map when reading, but attempts to write to a nil map will cause a runtime panic; don't do that. To initialize a map, use the built in make function:m = make(map[string]int),nil slice问题官方文档解释如下:nil map doesn't point to an initialized map. Assigning value won't reallocate point address.The append function appends the elements x to the end of the slice s, If the backing array of s is too small to fit all the given values a bigger array will be allocated. The returned slice will point to the newly allocated array.

  1. 使用make()初始化
package main

import "fmt"

func main() {
	// 使用make声明并分配内存
	cityMap := make(map[int]string)
	// 初始化map
	cityMap[1] = "北京"
	cityMap[2] = "上海"
	cityMap[3] = "广州"
	cityMap[4] = "深圳"
	// 遍历map
	for key, value := range cityMap {
		fmt.Println(key, value)
	}
}

注:不能使用make()初始化map并同时进行赋值,错误示例如下:

// 使用make同时声明和为map赋值(错误)
cityMap := make(map[string]string){
    "广州": "小蛮腰",
    "上海": "东方明珠",
    "北京": "故宫",
    "杭州": "西湖",
}

总结:

map的赋值一共有三种方式:

  1. 在使用map关键字声明时同时进行赋值初始化
  2. 使用map关键字声明后,使用make()函数为其分配内存地址进行初始化,再进行赋值
  3. 使用make()函数同时声明和初始化,再进行赋值

3.3. 特殊类型作为value值

  1. 切片作为value值

正常情况下,keyvalue一一对应,但有些情况下,可能需要一个key对应多个value值,此时可以通过将value定义为切片类型来实现。

package main

import "fmt"

func main() {
	hobbyMap := make(map[string][]string)
	hobbyMap["张三"] = []string{"唱歌", "跳舞"}
	hobbyMap["李四"] = []string{"跑步", "游泳"}
	for k, v := range hobbyMap {
		fmt.Printf("%s的爱好是:%v\n", k, v)
	}
}
//张三的爱好是:[唱歌 跳舞]
//李四的爱好是:[跑步 游泳]
  1. map作为value值

示例:

  • 使用map[string]map[string]sting的map类 型
  • key:表示用户名,是唯一的,不可重复
  • 如果某个用户名存在,就将其密码修改"888888",如果不存在就增加这个用户信息(包括昵称nickname和密码 pwd)
  • 编写一个函数modifyUser(users map[string]map[string]sting, name string)完成上述功能
package main

import "fmt"

func modifyUser(users map[string]map[string]string, name string) {

	if users[name] != nil {
		// 用户存在密码改为888888
		users[name]["pwd"] = "888888"
	} else {
		// 用户不存在,添加用户信息
		users[name] = make(map[string]string, 2)
		users[name]["pwd"] = "888888"
		users[name]["nickname"] = "小小" + name //示意
	}
}

func main() {

	users := make(map[string]map[string]string, 10)
	users["张三"] = make(map[string]string, 2)
	users["张三"]["nickname"] = "zs"
	users["张三"]["pwd"] = "1111111"

	modifyUser(users, "李四")
	modifyUser(users, "王五")

	for k, v := range users {
		fmt.Println(k, v)
	}
}
//李四 map[nickname:小小李四 pwd:888888]
//张三 map[nickname:zs pwd:1111111]     
//王五 map[nickname:小小王五 pwd:888888]

3.4. 增删改查

  1. 增加和更新

map[key] = value,如果key不存在于map中,则会对map增加一个键值对;如果key已经存在,则会对map中key对应的value值进行更新。

  1. 删除

Go语言提供了一个内置函数delete(),用于删除容器内的元素,delete()函数的语法格式:

delete(mapName, key),函数无返回值,当删除不存在的key时不会报错。

示例:

package main

import "fmt"

func main() {
	cityMap := make(map[int]string)
	cityMap[1] = "北京"
	cityMap[2] = "上海"
	cityMap[3] = "广州"
	cityMap[4] = "深圳"
	// 删除元素
	delete(cityMap, 1)
	for key, value := range cityMap {
		fmt.Println(key, value)
	}
}

注:使用delete()每次只能删除一对键值对,Golang并没有提供清空所有元素的方法,如果需要对map进行清空,可以对key进行遍历逐个删除或者map = make(...),则原来的map指向的底层数据结构会被Golang的垃圾回收机制进行回收。

  1. 查找

可以通过key获取map中对应的value值,语法格式为:map[key]。如果key存在,则返回对应的value值;如果key不存在,则返回value类型的默认值,如value的类型为int,当key不存在时,返回的为0,在Golang中操作map,无论key是否存在都不会出现panic或者返回error

但是在很多情况下,需要判断key:value是否存在,为此,Golang通过在使用map[key]时返回第二参数来标识key是否存在,语法格式为:value, ok := map[key],当key存在时,返回的第二参数为true;当key不存在时,返回的第二参数为false。

func main() {
    dict := map[string]int{"key1": 1, "key2": 2}
    value, ok := dict["key1"]
    if ok {
        fmt.Printf(value)
    } else {
        fmt.Println("key1不存在...")
    }
}

3.5. map遍历

可以通过for...range对map进行遍历,遍历时,同时返回key和value。

在某些情况下,如果只需要获得value,可以使用下划线_来替代key,示例如下:

package main

import "fmt"

func main() {
	dict := map[int]string{
		1: "张三",
		2: "李四",
		3: "王五",
	}
	// 只遍历value值
	for _, value := range dict {
		fmt.Println(value)
	}
}
//李四
//王五
//张三

在只需要遍历key时,可以使用如下形式:for key := range dict,无需使用匿名变量:

package main

import "fmt"

func main() {
	dict := map[int]string{
		1: "张三",
		2: "李四",
		3: "王五",
	}
	// 只遍历key值
	for key := range dict {
		fmt.Println(key)
	}
}
//1
//2
//3

注:Golang中的map默认是无序的,遍历的结果顺序与填充的顺序无关,可能每次遍历返回的结果都不同,如果需要返回特定顺序的结果,需要进行排序。步骤如下:

  1. 先将map中的key存入切片中
  2. 对切片进行排序
  3. 遍历切片,按照key输出map的value
package main

import (
	"fmt"
	"sort"
)

func main() {
	dict := make(map[int]int)
	dict[2] = 2
	dict[5] = 8
	dict[1] = 10
	dict[6] = 1
	dict[4] = 9
	// 声明一个切片保存map的key
	var slice []int
	// 将map数据遍历保存到slicefor key := range dict {
		slice = append(slice, key)
	}
	// 对切片进行排序
	sort.Ints(slice)
	// 打印切片
	fmt.Println(slice)
	// 根据排序的key输出value
	for _, key := range slice {
		fmt.Printf("map[%v] = %v\n", key, dict[key])
	}
}

3.6. map切片

当切片的数据类型为map时,称之为 slice of map,此时map的个数就能动态变化。

示例:使用map切片,map中保存学生的个人信息,包含name和age。

package main

import "fmt"

func main() {
	// map切片,放入2个map
	slice := make([]map[string]string, 2)
	// 增加第一个学生的信息
	if slice[0] == nil {
		slice[0] = make(map[string]string, 2)
		slice[0]["name"] = "张三"
		slice[0]["age"] = "18"
	}
	// 增加第二个学生的信息
	if slice[1] == nil {
		slice[1] = make(map[string]string, 2)
		slice[1]["name"] = "李四"
		slice[1]["age"] = "20"
	}
	fmt.Println(slice)
}
//[map[age:18 name:张三] map[age:20 name:李四]]

3.7. 其他

  1. map是引用类型,遵守引用类型传递机制,一个函数在接收map后,对其进行修改,会直接修改原来的map,或者将一个map变量赋值给另一个变量时,它们都指向同一个底层map,相互之间会产生影响。
  2. map的容量是不固定的,可以动态变化,当向map中增加一个元素时,会自动进行扩容。
  3. map经常使用struct结构体作为value值,可以实现更为复杂的数据存储和管理。

3.8. sync.Map

Go语言中map如果在并发读的情况下是线程安全的,如果是在并发写的情况下,则是线程不安全的。Golang 为我们提供了一个 sync.Map 是并发写安全的。

Golang 中的 map 的 key 和 value 的类型必须是一致的,但 sync.Map 的 key 和 value 不一定是要相同的类型,不同的类型也是支持的。