Go语言内置容器主要有数组,切片和映射
数组:
定义:具有类型相同,长度固定且在对应存放在内存中的一块连续区域的数据结构。
声明数组:var 数组变量名 [数组长度]元素类型
初始化数组:var 数组变量名 = [数组长度]元素类型{元素}
也可不设置数组长度用…代替,go语言编译器根据元素个数来设置数组大小。
func main(){
var student [3]string
student = [3]string{"Tom","Allen","Peter"}
class := [...]string{"一年一一班","一年级二班"}
fmt.Println(student)
fmt.Println(calss)
}
//结果为:
[Tom Allen Peter]
[一年级一班 一年级二班]
切片:
定义:同样表示多个同类型元素的连续集合
切片本身并不存储任何元素,只是对现有数组的引用。即它本身就是一个指针
切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型,这就导致它们不需要使用额外的内存并且比使用数组更有效率,所以在Go 代码中切片比数组更常用。
切片在内存中的结构包括:地址,长度,容量
地址:切片第一个元素所指向的内存地址,其地址与截取的数组或切片开始位置对应的元素地址相同。
长度:切片中实际元素的个数
容量:从切片的起始元素开始到其底层数组中的最后一个元素的个数
生成方式:从数组生成一个新切片,从切片生成一个新切片,创建一个新切片
func main(){
student := [...]string{"Tom","Alen","Ming","Peter","Alice"}
studentSlice1 := student[1:3] //从数组生成新切片
studentSlice2 := studentSlice1[:1] //从切片生成新切片
fmt.Printf("student数组为:%v,地址为:%v\n",student,&student[1])
fmt.Printf("studentSlice1切片为:%v,地址为:%v,长度为:%d,容量为:%d\n",studentSlice1,&studentSlice1[0],len(studentSlice1),cap(studentSlice1))
fmt.Printf("studentSlice2切片为:%v,地址为:%v,长度为:%d,容量为:%d",studentSlice2,&studentSlice2[0],len(studentSlice2),cap(studentSlice2))
}
结果为:
注意:新生成的切片不包括结束位置对应的元素
声明切片:var 切片变量名 []元素类型
声明未初始化为空切片,默认值为nil
初始化切片:
切片名 := []元素类型{元素} 元素不能为空,即不能用短变量命名空切片
make([]元素类型,切片长度,切片容量(可选))
注意:切片容量值必须大于长度,对切片容量应该有个大概的估值,若容量值过小,对切片多次扩充会造成性能损耗(相当于对底层数组扩充)。
添加元素:
append 当切片不能容纳其他元素时,即切片长度等于容量,再一次append容量会按两倍进行扩充。
注意:如果切片从其他切片或者数组生成(必须是起始位置和结束位置都在数组容量范围内,否则不会覆盖),对新切片元素添加,删除需要考虑对原有数组或切片中数据的影响,覆盖数组对应元素。
删除元素:
删除切片元素没有方法,需要手动将删除点前后的元素连接起来,从而实现对切片中元素的删除。
清空切片所有元素:切片名 = 切片名[0:0]
func main(){
var student []string //声明切片
studentArr := [...]string{"Tom","Alice","Peter"}
studentSlice := studentArr[:1]
class := []string{"五年级一班","五年级二班"} //初始化切片
teacher := make([]string,1) //初始化切片
fmt.Printf("判断student切片是否为空:%v,student:%v\n",student==nil,student)
fmt.Printf("判断teacher切片是否为空:%v,teacher:%v\n",teacher==nil,teacher)
fmt.Printf("class切片长度为:%d,容量为:%d\n",len(class),cap(class))
class = append(class,"五年级三班")
fmt.Printf("class切片扩充后长度为:%d,容量为:%d\n",len(class),cap(class))
studentSlice = append(studentSlice,"Ming","Marshall")
fmt.Println("studentArr为:",studentArr)
fmt.Println("studentSlice切片为: ",studentSlice)
studentSlice = append(studentSlice[:1],studentSlice[2])
fmt.Printf("studentSlice切片删除后为%v,长度为:%d,容量为%d\n",studentSlice,len(studentSlice),cap(studentSlice))
fmt.Println("studentArr为:",studentArr)
}
结果为:
映射:一种无序的键值对的集合
声明映射: var map[键类型]值类型
声明未初始化默认为nil
初始化映射, 区别于切片
映射名 := map[键类型]值类型{元素} 元素可为空
make(map[键类型]值类型,map容量(可选))
可以不指定容量,但是多次扩充造成性能损耗。
添加键值对
映射名[键]=值
删除键值对:
delete(map(实例),键)
Go语言中没有为map提供清空所有元素的方法,要想清空的唯一方法是重新定义一个Map。
应用场景:适合存放有关联关系的数据
func main(){
var student map[string]int //默认为nil
class := map[string]int{} //不为nil
teacher := make(map[string]int,1)
teacher["Hu"] = 36
teacher["Han"] = 45
fmt.Println("student是否为nil:",student==nil)
fmt.Println("class是否为nil:",class==nil)
fmt.Println(teacher)
delete(teacher,"Hu")
fmt.Println(teacher)
}
结果为:
拓展:
make()和new()区别
两者都在堆上分配内存,但是它们的行为不同,适用于不同的类型
make()函数初始化,适用于引用类型,包括切片,映射,通道
new()函数分配内存,适用于值类型,如数组,结构体
range关键字
range通常配合for循环对数组,切片及映射进行迭代
range后接的表达式称为range表达式,迭代时,关键字range会返回两个值,由k,v接收,分别代表当前数组(切片,映射)的索引位置和该位置对应元素值的一份副本
range表达式 | 第一返回值 | 第二返回值 |
---|---|---|
数组 | 元素下标 | 元素值 |
切片 | 元素下标 | 元素值 |
映射 | 键 | 值 |
通道 | 元素 | N/A |
并发访问map(非并发还是推荐map)
通常对map的操作都是单协程的,如果多个协程并发访问同一个map就会发生异常,所以map不是协程安全的,同一时刻只能有一个协程对map操作。
func main(){
goMap := make(map[int]int)
for i := 0;i < 10000;i++{
go writeMap(goMap,i,i)
go readMap(goMap,i)
}
}
func writeMap(goMap map[int]int,key,value int){
goMap[key] = value
}
func readMap(goMap map[int]int,key int) int{
return goMap[key]
}
结果为:
最常见的解决办法是使用sync包对map加锁或者直接在go1.9版本中提供的线程安全map。
var lock sync.RWMutex
func main(){
goMap := make(map[int]int)
for i := 0;i < 10000;i++{
go writeMap(goMap,i,i)
go readMap(goMap,i)
}
fmt.Println("读写完成!")
}
func writeMap(goMap map[int]int,key,value int){
lock.Lock() //写之前加锁
goMap[key] = value
lock.Unlock() //写完解锁
}
func readMap(goMap map[int]int,key int) int{
lock.Lock()
value := goMap[key]
lock.Unlock()
return value
}
结果为:
由于加锁会影响程序性能,建议使用效率较高的并发安全的sync.Map
sync.Map特点:
内部通过冗余的数据结构降低加锁对性能的影响
使用前无须初始化,直接声明即可(无须make)
不使用map中的方式进行读取和赋值操作,而是使用Load()和Store()方法
sync.Map没有提供获取map数量的方法,解决方案是通过循环遍历map。
func main(){
var goMap sync.Map
for i := 0;i < 10000;i++{
go writeMap(goMap,i,i)
go readMap(goMap,i)
}
fmt.Println("读写完成!")
}
func writeMap(goMap sync.Map,key,value int){
goMap.Store(key,value) //线程安全设置
}
func readMap(goMap sync.Map,key int) int {
res, ok := goMap.Load(key) //线程安全读取 ---断言
if ok {
return res.(int)
}
return 0
}
结果同上