go的数组、切片、Maps,并发安全sync.map
数组
1、定义数组
package main
import "fmt"
func main() {
//定义了一个大小为3的int类型数组
//数组在定义阶段,大小和类型就固定了
var a [3]int //只定义,没有初始化
fmt.Println(a)
}
//[0 0 0]
2、使用数组
package main
import "fmt"
func main() {
var a [3]int
a[2]=100 //修改赋值
fmt.Println(a)
fmt.Println(a[0]) //取0的位置
}
//[0 0 100]
//0
3、定义并赋值
package main
import "fmt"
func main() {
//方式一
var a [3]int=[3]int{1,2,3} //[1 2 3]
//方式二
var a =[3]int{1,2,3} //[1 2 3]
//方式三
a := [3]int{1, 2, 3} //[1 2 3]
//只给第2个位置设为99
a := [3]int{2:99} //[0 0 99]
//指定位置设置
a := [3]int{2:99,1:88} //[0 88 99]
fmt.Println(a)
}
4、数组的大小是类型的一部分
package main
import "fmt"
func main() {
//这两个不是一个类型
var a [2]int
var b [3]int
a=b
fmt.Println(a>b)
}
5、数组是值类型(当参数传递到函数中,修改不会改变原来的值)
// go语言中,都是copy传递
/*
python中都是引用传递,一切皆对象,就是地址,当做参数传递是把地址传过去了
python中比较特殊:可变类型和不可变类型
*/
package main
import "fmt"
func main() {
var a [3]int=[3]int{5,6,7}
fmt.Println(a) //[5 6 7]
test1(a)
fmt.Println(a) //[5 6 7]
}
func test1(a [3]int) {
a[0]=999
fmt.Println(a) //[999 6 7]
}
6、数组长度
package main
import "fmt"
func main() {
var a [3]int=[3]int{5,6,7}
fmt.Println(len(a)) //3
}
7、循环数组
package main
import "fmt"
func main() {
//方式一
var a [3]int=[3]int{5,6,7}
for i:=0;i<len(a);i++{
fmt.Println(a[i])
}
//方式二
//range:是一个关键字
var a [3]int=[3]int{5,6,7}
for i,v:=range a{
fmt.Println(i) //索引
fmt.Println(v) //数组的值
}
//方式三
//函数如果返回两个值,必须用两个值来接收
//range可以用一个值来接收,如果用一个值来接收,就是索引
var a [3]int=[3]int{5,6,7}
for i:=range a{
fmt.Println(i) //索引
}
//方式四
//只取值,不取索引
var a [3]int=[3]int{5,6,7}
for _,v:=range a{
fmt.Println(v) //值
}
}
8、多维数组
package main
import "fmt"
func main() {
//定义一个二维数组
var a [3][2]int // [[0 0] [0 0] [0 0]]
//定义并初始化
var a [3][2]int=[3][2]int{{1,2},{4,5},{9,70}}
a[1][0]=999 //修改值
fmt.Println(a) // [[1 2] [999 5] [9 70]]
}
切片
//切片是由数组建立的一种方便、灵活且功能强大的包装(Wrapper)。切片本身不拥有任何数据。它们只是对现有数组的引用。
//切片底层依附于数组
1、创建切片
package main
import "fmt"
func main() {
//创建切片(基于一个数组创建)
var a [9]int=[9]int{1,2,3,4,5,6,7,8,9}
//基于数组,切出一部分成为切片
// []int 中括号中没有东西,就是切片
var b []int
// 没有-1 没有步长
b=a[0:3] //前闭后开 //[1 2 3]
b=a[3:6] //前闭后开 //[4 5 6]
b=a[:] //前闭后开 //[1 2 3 4 5 6 7 8 9]
b=a[3:] //前闭后开 //[4 5 6 7 8 9]
fmt.Println(b)
}
2、使用切片
package main
import "fmt"
func main() {
var a [9]int=[9]int{1,2,3,4,5,6,7,8,9}
var b []int
b=a[3:] //前闭后开 //[4 5 6 7 8 9]
fmt.Println(b[0]) // 4
}
3、切片的修改会影响底层数组,数组的修个也会影响切片。相互影响
package main
import "fmt"
func main() {
var a [9]int=[9]int{1,2,3,4,5,6,7,8,9}
var b []int=a[0:3] //前闭后开
fmt.Println(a) //[1 2 3 4 5 6 7 8 9]
fmt.Println(b) //[1 2 3]
a[0]=999
b[2]=888
fmt.Println(a) //[999 2 888 4 5 6 7 8 9]
fmt.Println(b) //[999 2 888]
}
4、切片的长度和容量
长度和容量区别:
a := make([]int,3,10) //长度3,容量10 [0 0 0]
长度:初始长度只有3,随切片追加的实际长度而改变。比如append(a, 1)追加一个值,长度变为4,容量还是10
容量:初始cap(a)容量为10,当追加的值超过了容量10,则容量翻倍变为20。
//总结1:当切片追加值,超过了切片容量,切片容量会翻倍,在原来容量基础上乘以2
//总结2:一旦超过了原数组, 就会重新申请数组,把数据copy到新数组,切片和原数组就没有关系了
//总结3:追加值打破了原数据的容量,就不会再影响原数据
1.18版本以前:容量小于1024翻倍,大于1024增加1/4
1.18版本:容量小于1024翻倍,大于1024cap=原切片长度+新增元素个数
package main
import "fmt"
func main() {
var a [9]int=[9]int{1,2,3,4,5,6,7,8,9}
var b []int=a[2:3] //前闭后开 //[3]
fmt.Println(len(b)) //1
//切片容量是7,意思是,可以往里追加值
fmt.Println(cap(b)) //7 还可以往后追加7个
//容量就是原数组的长度呗?对不对?不太对。。
b[0]=9999
fmt.Println(a) //[1 2 9999 4 5 6 7 8 9]
fmt.Println(b) //[9999]
}
5、追加值
//总结1:当切片追加值,超过了切片容量,切片容量会翻倍,在原来容量基础上乘以2
//总结2:一旦超过了原数组, 就会重新申请数组,把数据copy到新数组,切片和原数组就没有关系了
//总结3:追加值打破了原数据的容量,就不会再影响原数据
package main
import "fmt"
func main() {
var a [9]int=[9]int{1,2,3,4,5,6,7,8,9}
var b []int=a[2:3] //前闭后开 //[3]
b=append(b,1) //追加1
b=append(b,11,22,33,44,55) //继续追加
fmt.Println(len(b)) //7
fmt.Println(cap(b)) //7
fmt.Println(b) // [3 1 11 22 33 44 55]
fmt.Println(a) // [1 2 3 1 11 22 33 44 55]
//到了数组的尾部,继续追加值
b=append(b,999)
fmt.Println(len(b)) // 8
fmt.Println(cap(b)) //容量是14
//总结1:当切片追加值,超过了切片容量,切片容量会翻倍,在原来容量基础上乘以2
b=append(b,222,333,444,555,666,7,8)
fmt.Println(len(b)) //15
fmt.Println(cap(b)) //容量是14*2=28
//总结2:一旦超过了原数组, 就会重新申请数组,把数据copy到新数组,切片和原数组就没有关系了
fmt.Println(a)
fmt.Println(b)
a[8]=7777
fmt.Println(a)
fmt.Println(b)
}
6、通过make创建切片(底层也依附于数组)
a := make([]int,3) //长度3,容量3
package main
import "fmt"
func main() {
//var a []int
//切片零值是什么? nil类型:是所有引用类型的空值
//fmt.Println(a)
//if a==nil{
// fmt.Println("我是空的")
//}
//3是长度,4是容量
//var a []int=make([]int,3,4) //[0 0 0]
//3是长度,3是容量
var a []int=make([]int,3) //[0 0 0]
a := make([]int,3) //第二种
fmt.Println(a)
fmt.Println(len(a)) //3
fmt.Println(cap(a)) //3
a=append(a,55) // [0 0 0 55]
fmt.Println(a)
fmt.Println(len(a)) //4
fmt.Println(cap(a)) //6
}
7、切片定义并赋初值
package main
import "fmt"
func main() {
var a []int=[]int{1,2,3}
fmt.Println(a) //[1 2 3]
fmt.Println(len(a)) //3
fmt.Println(cap(a)) //3
}
8、切片是引用类型,当参数传递,会修改掉原来的值
package main
import "fmt"
func main() {
var a []int=[]int{1,2,3}
fmt.Println(a) // [1 2 3]
test3(a)
fmt.Println(a) // [999 2 3]
}
func test3(a []int) {
a[0]=999
fmt.Println(a) // [999 2 3]
}
9、多维切片
package main
import "fmt"
func main() {
var a [][]int=make([][]int,2,3)
fmt.Println(a) //[[] []]
fmt.Println(a[0]==nil) //true
//会报错
//a[0][0]=999
// 如何做?(for循环完成初始化)
a[0]=make([]int,2,3)
a[0][1]=999
fmt.Println(a) //[[0 999] []]
a[1][0]=99999
//
////定义并赋初值用的多
//var a [][]int=[][]int{{1,2,3},{4,5,6,7,7,8}}
//////跟上面不一样
//var a [][3]int=[][3]int{{1,2,3},{4,5,6}}
//fmt.Println(a)
}
10、切片的copy
package main
import "fmt"
func main() {
var a []int=make([]int,3,4)
var b []int=make([]int,2,6)
fmt.Println(a) //[0 0 0]
fmt.Println(b) //[0 0]
a[0]=11
a[1]=22
a[2]=33
b[0]=999
fmt.Println(a) // [11 22 33]
fmt.Println(b) // [999 0]
copy(b,a) //拷贝,把a拷给b,多余的截断
fmt.Println(b) // [11 22]
}
11、切片越界
package main
import "fmt"
func main() {
var a []int=make([]int,3,4)
fmt.Println(a) //[0 0 0]
a[0]=11
a[1]=22
a[2]=33
fmt.Println(a) //[11 22 33]
a=append(a,999) //追加
fmt.Println(a) //[11 22 33 999]
//中括号取值,只能取到长度值,不能取到容联大小
fmt.Println(a[3]) //999
fmt.Println(a[4]) //越界报错
}
12、切片append使用,追加切片,删除i位置,删除i-j位置
1.将切片 b 的元素追加到切片 a 之后: a = append(a, b...)
2.删除位于索引 i 的元素: a = append(a[:i], a[i+1:]...)
3.切除切片 a 中从索引 i 至 j 位置的元素: a = append(a[:i], a[j:]...)
4.在索引i位置插入元素:
nums1 := []int{1, 2, 3, 4}
nums1 = append(nums1[:2], append([]int{66}, nums1[2:]...)...)
fmt.Println(nums1) //[1 2 66 3 4]
5.在索引i位置插入切片:
nums1 := []int{1, 2, 3, 4}
nums2 := []int{66, 66, 66}
nums1 = append(nums1[:2], append(nums2, nums1[2:]...)...)
fmt.Println(nums1) //[1 2 66 66 66 3 4]
6.在头部位置插入元素:
a := []int{1, 2, 3}
a = append([]int{66}, a...)
fmt.Println(a) //[66 1 2 3]
7.在头部位置插入切片:
a := []int{1, 2, 3}
a = append([]int{66,77}, a...)
fmt.Println(a) //[66 77 1 2 3]
8.弹出切片末尾的元素:
nums1 := []int{1, 2, 3, 4}
x, nums1 := nums1[len(nums1)-1], nums1[:len(nums1)-1]
fmt.Println(x)//4
fmt.Println(nums1)//[1 2 3]
9.弹出切片头部的元素:
nums1 := []int{1, 2, 3, 4}
x, nums1 := nums1[0], nums1[1:]
fmt.Println(x) //1
fmt.Println(nums1) //[2 3 4]
13、copy
复制切片 a 的元素到新的切片 b 上:
nums1 := []int{1, 2, 3, 4}
b := make([]int, len(nums1))
copy(b, nums1)
fmt.Println(b)
Maps
//maps:hash,字典, key:value存储
1、map的定义和使用
package main
import "fmt"
func main() {
//map[key类型]value类型:key的类型必须可hash,key值:数字,字符串
//map的零值:nil 它是一个引用类型
var a map[int]string
a := make(map[int]string)
fmt.Println(a) //map[]
if a==nil{
fmt.Println("我是空的") // 我是空的
}
}
2、创建map
dic := make(map[string]string) //方式一
var dic map[int]string=make(map[int]string) //方式二
dic := map[string]string{"pwd":"123445"} //方式三
package main
import "fmt"
func main() {
//var a map[int]string
//初始化用make
var a map[int]string=make(map[int]string)
//如果有,会修改,如果没有,会放入
a[1]="lqz" // 设值
a[1]="jeff" //覆盖
a[2]="18"
//a["xx"] key值不能乱写
fmt.Println(a) //map[1:jeff 2:18]
}
3、获取元素
package main
import "fmt"
func main() {
var a map[int]string=make(map[int]string)
//var a map[int]int=make(map[int]int)
fmt.Println(a[0]) //取出value值的空值 ""
fmt.Println(a) // map[]
}
4、判断value值是否存在
package main
import "fmt"
func main() {
//统一的方案来判断value值是否存在
//a[0] 可以返回两个值,一个是value值(可能为空),另一个是true或false
var a map[int]int=make(map[int]int)
a[0]=0
v,ok:=a[0]
fmt.Println(v) //0
fmt.Println(ok) //true
}
5、map删除元素
package main
import "fmt"
func main() {
var a map[int]int=make(map[int]int)
a[1]=11
a[2]=22
fmt.Println(a) //map[1:11 2:22]
//根据key删(内置函数)
delete(a,1)
fmt.Println(a) //map[2:22]
}
6、map长度
package main
import "fmt"
func main() {
var a map[int]int=make(map[int]int)
fmt.Println(len(a)) //0
a[1]=11
a[2]=22
fmt.Println(len(a)) //2
}
7、map 是引用类型
package main
import "fmt"
func main() {
var a map[int]int=make(map[int]int)
a[1]=11
fmt.Println(a) // map[1:11]
test(a)
//证明是引用类型,相互改变
fmt.Println(a) // map[1:999]
}
func test(a map[int]int) {
a[1]=999
fmt.Println(a) //map[1:999]
}
8、map的相等性
//和python的字典差不多,只能按key取值后比较
package main
import "fmt"
func main() {
var a map[string]string=make(map[string]string)
a["name"]="lqz"
var b map[string]string=make(map[string]string)
b["name"]="lqz"
fmt.Println(a)
fmt.Println(b)
//不能这样判断,map只能跟nil比较
if a["name"]==b["name"] {
fmt.Println("相等")
}
}
9、循环map
//range循环
package main
import "fmt"
func main() {
var a map[string]string=map[string]string{"name":"lqz","age":"19","sex":"男"}
for k,v:=range a{
fmt.Println(k) //key
fmt.Println(v) //value
}
}
//map是无序的(python中字典从3.6以后有序了,3.6之前无序,底层如何实现的)
10、map的健支持的类型
map的健可以是:int,string,float,bool,[2]int,必须是可hash的,切片不可hash
dic1 := make(map[int]int)
dic2 := make(map[struct{}]int)
dic3 := make(map[float32]int)
dic4 := make(map[bool]int)
dic5 := make(map[[2]int]int)
dic5 := make(map[[]int]int) //报错
sync.map并发安全map使用场景
sync.map结构体
type Map struct {
mu Mutex
read atomic.Value // readOnly,只读的map,无需锁
dirty map[interface{}]*entry //只写的map,需要使用锁
misses int //从dirty刷到read中的指标
}
sync.map提供的5个方法
var ma sync.Map //该类型是开箱即用,只需要声明既可
ma.Store("key", "value") // 存储值
ma.Delete("key") //删除值
ma.LoadOrStore("key", "value")// 获取值,如果没有则存储
fmt.Println(ma.Load("key"))//获取值
//遍历
ma.Range(func(key, value interface{}) bool {
fmt.Printf("key:%s ,value:%s \n", key, value)
//如果返回:false,则退出循环,
return true
})
sync.map总结
合理使用go语言提供的标准库就可以写出高效率的代码,map
sync.map
1.sync.map的核心实现是两个map,一个用于写(加锁),一个用于读(无锁),这样设计的思想好比缓存与数据库
2.sync.map的局限性,如果写高于读(写加锁,刷数据到read都需要耗费资源),dirty>read,刷新数据的频率比较高,性能急剧下降。此时不如直接改造标准库map,rwmutex+map组合。
3.sync.map的设计思想--保证高频的读无锁,两个map。空间换时间
选择了IT,必定终身学习