Go基础语法(持续更新中...)
Tips
js解析整数为浮点数时,int64可能溢出
type Request struct {
ID int64 `json:"id,string"` //加上 tag:json:"id:string"
}
异常报错
// 1. 数组/切片索引越界
panic: runtime error: index out of range [x] with length x
// 2. 空指针调用
panic: runtime error: invalid memory address or nil pointer dereference
// 3. 过早关闭HTTP响应体
panic: runtime error: invalid memory address or nil pointer dereference
// 4. 除以0
panic: runtime error: integer divide by zero
// 5. 向已关闭的chan发送s信息
panic: send on closed channel
// 6. 重复关闭chan
panic: close of closed channel
// 7. 关闭未初始化的通道
panic: close of nil channel
// 8. 未初始化map
panic: assignment to entry in nil map
// 9. 跨协程的异常处理
panic: runtime error: integer divide by zero
// 10. sync计数为负值
panic: sync: negative WaitGroup counter
变量
// 方法1
var y int // 声明变量类型
var x string = "abc" // 声明类型并赋值
var s1, s2 = "he", 12 // 一行定义变量
// 方法2
var(
a string // 默认值为空字符串
b int // 默认值为0
c bool // 默认值为false
d = "ABC" // 字符串赋值必须是双引号!
)
// 方法3
func h() {
a1, b1, c1 := 1, "str", true // 声明变量并赋值,只能在函数内使用:= 推荐
}
常量(定义后不可变的值)
在 Go 语言中,只允许布尔型、字符串、数字类型这些基础类型作为常量。
- iota 枚举,只能在常量中使用,iota默认是0
- 遇到const iota就初始化为零
- const中每新增一行变量声明iota就递增1
- const声明如果不写,默认就和上一行一样
const filename = "abc.log" // 定义常量
const(
A = iota + 1 // 输出1,也可以减n把初始值变成负数
B // 输出2
_ // 单下划线表示跳过值
C // 输出4
)
数据类型
字符串(string)
小结
使用双引号表示字符串
使用单引号表示字符
``表示换行字符串,里面使用(转义符号),不生效,表示字符串
字符串格式化
%s // 表示字符串
%q // 打印原始字符串
%p // 显示内存地址,指针的格式化标识符为%p
%d // 整数
%c // 表示字符
%t // 布尔值
%T // 查看变量类型
%b // 打印二进制
%f // 表示浮点数
%% // 可以打印百分号,用%转以
%v // 默认占位符,值的默认格式表示
%+v // 类似%v,但输出结构体时会添加字段名
%#v // 完全打印所有的字段,可以打印结构体每个成员的名字
字符串处理
package main
import (
"fmt"
"strings"
)
var s = `多行
文本
字符串
`
var(
s0 = "[abc,def,hig]"
s1 = "My name is"
s2 = "Age is"
a1 = '你' //单引号定义字符
)
//自定义函数,分割多个字符的
func SplitByMoreStr(r rune) bool {
return r == '[' || r == ']' || r == ',' || r == '"'
}
func main() {
//字符串拼接
fmt.Println(s1 + "cby" + " " + s2 + "26")
me := fmt.Sprintf("%s cby %s 26",s1,s2)
fmt.Println(me)
//分割
fmt.Println(strings.Split(s1, "")) //默认会把每一个字符串元素分割成切片
//分割多个字符
x := strings.FieldsFunc(s0, SplitByMoreStr)
//判断包含,相当于python的 "xxx" in s
fmt.Println(strings.Contains(s1, "name"))
//判断前后缀字符串
fmt.Println(strings.HasPrefix(s1, "My")) //判断前,返回布尔
fmt.Println(strings.HasSuffix(s1, "is")) //判断后,返回布尔
//判断元素的位置
s3 := "apendp"
fmt.Println(strings.Index(s3,"p"))
fmt.Println(strings.LastIndex(s3,"p"))
//切片分割成字符串
s4 := []string{"python", "php", "Go"}
fmt.Println(strings.Join(s4,"-")) //把切片用"-"连接成一个字符串
//for range 循环 是按照rune类型去遍历的
s5 := "hello 世界"
for _,s := range s5{
fmt.Printf("%c
",s)
}
}
字符串反转
package main
import "fmt"
func main() {
s := "hello"
s1 := []byte(s) // 使用[]rune(s)也可以
r := ""
for i := len(s1) - 1; i >= 0; i-- {
r += string(s1[i])
}
fmt.Println(r)
}
修改字符串
要修改字符串需要将其转化成 []rune 或者 []byte,完成后在转换成string,无论哪种转换都会重新分配内存,并复制字节数组。
package main
import "fmt"
func main() {
s1 := "big"
b := []byte(s1)
b[0] = 'p'
fmt.Println(string(b)) //输出 pig
s2 := "你好"
r := []rune(s2)
r[0] = '和'
fmt.Println(string(r)) //输出 和好
}
byte和rune
英文字符用byte(ASCII码表示) 010101rune(中文,UTF8编码)
rune类型专门处理Unicode
整数型(int)
// 有符合整型,可以是复数、零和整数。
int 没有具体bit大小的整数,根据硬件设备cpu有关
int8 范围 -128到127
int16 范围 -32768到32767
int32 范围 -2147483648到2147483647
int64 范围 -9223372036854775808到9223372036854775807
// 无符合整型,只能是零和整数。
uint 没有具体bit大小的整数,根据硬件设备cpu有关
uint8 范围 0到255
uint16 范围 0到65535
uint32 范围 0到4294967295
uint64 范围 0到18446744073709551615
浮点型
float32 flot64 浮点数都是不精确的。
浮点运算方法:
1.转化成字符串去做运算
2.整体放大多少倍换成整数进行运算
复数(不常用)
complex64
complex128
逻辑运算与判断循环
逻辑运算符
&& // 并且,返回布尔值
|| // 并且有一个成立就为真,返回布尔值
! // 原来为真取非,原来为假则为真
if !false || !false {pass} // 判断布尔值
判断语法
if {
} else if {
} else {
}
switch语句
switch x {
case "a","b":
fmt.Println("1")
case x>10 && x<20:
fmt.Println("1")
default:
fmt.Println("2")
for循环
for i:=0;i<=10;i++ {
// pass
}
// 死循环
for {
// pass
}
// for range循环
for i,x := range []int{1,2,3} {
// pass
}
数组
当把一个数组作为参数传入函数的时候,传入的其实是该数组的拷贝,而不是它的指针。如果要使用指针,需要 slice 类型。
数组必须都是同一类型的元素,长度也是类型的一部分。
数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。
package main
import "fmt"
var a [5]int //定义数组a,方法1
var b = [...]int{1,2,3,5,8} //定义并赋值数组,方法1
var(
a [5]int //定义数组a,方法2
s = [2]string{"name","age"} //定义并赋值数组,方法2
b = [...]int{1,2,3,5,8} //定义数组可以使用...省略
)
func main() {
a = [5]int{1,2,3,4,5} //赋值数组,方法1
a[0] = 9 //赋值数组,方法2
fmt.Println(a,s,b) //输出忽略
// 定义一个3个元素的数组,最后一个元素是1,其它的都是用0初始化
v := [...]int{2:1}
fmt.Println(v) // 输出:[0 0 1]
//根据索引值初始化数组
e := [5]int{1:22,3:33,0:11}
fmt.Println(e) //输出: [11 22 0 33 0]
s := [2]string{0:"呵呵",1:"你好"}
fmt.Println(s[0]) //输出: 呵呵
//定义多维数组,数组的类型为数组
//多维数组的定义,第一个为长度:代表有几行数组,可以使用[...],第二个为宽:代表一行元素的个数
a1 := [2][3]int{
{1,2,3},
{4,5,6},
}
fmt.Println(a1[0][1]) //输出 2
}
切片
切片底层使用的数组
数组和切片的区别:
-
容量是否可伸缩
-
是否可以比较
切片基础用法
package main
import (
"fmt"
"sort"
)
func main(){
var nilSlice []int // 空切片,等于nil,长度为0
slice := []int{} // 空切片,不等于nil,长度为0
fmt.Println(nilSlice == nil) //输出 true
fmt.Println(len(slice)) //输出 0
//基于数组得到切片
a := [5]int{1,2,3,4,5}
b := a[1:4]
c := b[:]
fmt.Println(b,c)
// make函数构造切片,初始化数据,如果切片没有初始化,默认等于nil
d := make([]int, 5, 10) //1类型,2切片的长度,3切片的容量(不写默认为长度)
fmt.Println(d)
// append函数,可以动态增加切片的容量,重新申请内存地址
sum := []int{1,2,3}
e := append(sum, b...) //append函数可以同时增加多个元素,append(sum, a, b)
fmt.Println(e)
//len函数查看长度,cap函数查看容量
a1 := []int{1,2,3,4,5}
a2 := make([]int, 5)
a3 := a2 //这样的写法,内存地址相等
copy(a2, a1) //把a1的内容拷贝到a2里,a1和a2虽然内容一样,但不在一个内存地址
a2[0] = 100
fmt.Println(a1) //输出:[1 2 3 4 5]
fmt.Println(a2) //输出:[100 2 3 4 5]
fmt.Println(a3) //输出:[100 2 3 4 5]
//sort排序 方法有Ints,Strings等
s := [...]int{10,22,8,2}
sort.Ints(s[:])
fmt.Println(s) //输出:[2 8 10 22]
}
结构体切片
// struct结构体切片用法
m := []struct{
i int
b bool
}{}
m = append(m, struct {
i int
b bool
}{i: 1, b: true})
fmt.Println(m) // 输出:[{1 true}]
// 自定义结构体切片用法
type mylist struct {
l int
name bool
}
// 使用双花括号{{}}可以为自定义结构体切片赋值
l := []mylist{{100, false}}
fmt.Println(l) // 输出:[{100 false}]
// 动态扩容结构体切片
m1 := []mylist{}
m1 = append(m1, mylist{10, false})
fmt.Println(m1[0]) // 输出:{10 false}
map
map基础用法
map是无序的,kv类型。
map的key不能为函数值。
func main(){
//声明map类型,但是没有初始化,表示还没有申请内存,d的初始值就是nil
var d map[string]int //k是string类型,v是int类型
fmt.Println(d == nil)
// map初始化,表示申请内存了
d = make(map[string]int, 5)
// 增加键值对
d["age"] = 18
fmt.Println(d["age"])
// 声明map同时完成初始化
// 方法1
score1 := map[string]int{"wsy":1,"old":2}
// 方法2
a := map[int]bool{
1: true,
2: false,
}
// 删除键值对
delete(a, 2)
// 判断key存不存在
var score = make(map[string]int, 5)
score["cby"] = 1
if v, ok := score["boy"]; ok{
fmt.Println(v)
}else{
fmt.Println("这个key不存在")
}
}
map key 排序
// map的key是无序的,有些时候需要有序
// 思路:遍历map把key存到切片里,排序切片里的key,然后遍历切片输出map[k]
func SortKey() {
d := map[int]string{
0: "a",
1: "b",
2: "c",
3: "d",
}
var n []int
for k, _ := range d {
n = append(n, k)
}
sort.Ints(n)
for _, i := range n {
fmt.Printf("%v
", d[i])
}
}
map实现值为结构体的使用方法
// 把map的值定义为结构体,并增加默认的字段
dict := make(map[string]struct{
name string
age int
})
type mystery struct {
name string
age int
}
dict["a"] = mystery{
name: "cby",
age: 16,
}
fmt.Println(dict["a"]) //输出:{cby 16}
map实现集合的方式
mySet := map[int]bool{} //key可以是任意类型,不能是动态可变的
mySet[1] = true
n := 3
if mySet[n]{
fmt.Println("存在",n)
}else{
fmt.Println("不存在",n)
}
map创建工厂
// v可以为函数
//m := make(map[int]func(op int) int, 5)
m := map[int]func(op int) int{}
m[1] = func(op int) int { return op }
m[2] = func(op int) int { return op * op }
m[1](10)
m[2](10) //调用函数
指针
指针是带类型的,如*int, *string
&取地址,*取值
func main() {
// var a *int //a是一个int类型的指针
// var c *[3]string
// 以上是错误的写法
var a = new(int) //得到一个int类型的指针
*a = 10
fmt.Println(*a)
// 初始化数组类型的指针
var c = new([3]int) //得到一个数组类型的指针
fmt.Println(c)
c[0] = 1 //或者 (*c)[0] = 1
fmt.Println(*c)
}
指针数组
a, b := 1, 2
pointArr := [...]*int{&a, &b}
fmt.Println("指针数组:", pointArr) // [0xc0... 0xc0...]
数组指针
arr := [...]int{1, 2, 3}
arrPoint := &arr
fmt.Println("数组指针:", arrPoint) // &[1 2 3]
make和new的区别
值类型:int, float, bool, string, array(数组), struct(结构体)
引用类型:map, slice(切片), func(函数), pointer(指针), channel
- new:是用来初始化值类型的指针的,并返回指针!返回的指针不是nil和空指针,指向了新分配的内存空间,里面存储的是零值。
- make:是用来初始化引用类型的,返回初始化后的非零值。
函数
函数的形参:
- 值类型的参数,参数是实参的拷贝,修改参数不会影响实参。
- 引用类型的参数,参数是实参的引用,修改参数会间接修改实参。
普通函数
//参数类型简写,相同类型的参数用逗号分隔宫格后边接类型
func f1(name1, name2 string){
fmt.Println(name1)
fmt.Println(name2)
}
//可变参数 0个或多个, 参数被看作该类型的切片
func sum(vals ...int) []int {
total := 0
for _, v := range vals {
total += v
}
vals = append(vals, total)
return vals
}
//调用上边的函数
l := []int{1, 2, 3}
sum(l...)
//无返回值
func f3(){
fmt.Println("没有返回值")
}
//有返回值
func f4(a, b int) int{
return a+b
}
//(闭包)返回带返回值的函数
func f5() func() int {
var x int
return func() int {
return x * x
}
}
//多返回值,必须用括号括起来,用英文逗号分隔
func f6(a, b int) (int, int){
return a+b, a-b
}
//命名的返回值,直接return就行
func f7(a, b int) (sum int, sub int){
sum = a + b
sub = a - b
return
}
匿名函数
//方法1
var MyFunc = func (sum int){
fmt.Println(sum)
}
func main(){
MyFunc(10)
// 方法2,匿名函数后边直接+括号调用
func (sum int){
fmt.Println(sum)
}(10)
}
闭包
闭包: 函数调用了它外层的变量。
这个函数返回值是个函数。
func closure(key string) func(name string){
return func(name string){
fmt.Println(name)
fmt.Println(key)
}
}
func main(){
// 闭包
f1 := closure("呵呵")// 得到闭包函数,接收closure函数返回值返回的函数
f1("cby")//调用闭包函数
f2 := closure("哈哈")// 得到闭包函数
f2("wsy")//调用闭包函数
}
异常处理
基本使用
- recover() 必须搭配defer使用
- defer一定要在可能引发panic的语句之前定义
package main
import "fmt"
func f1() {
// 定义个defer匿名函数用来捕获panic引发的异常
defer func() {
// recover
err := recover() //尝试将函数从当前的异常状态恢复过来
fmt.Println("recover抓到了panic异常", err)
}()
var a []int
a[0] = 100 //panic报错,因为切片没有被初始化
fmt.Println("f1") //不执行
}
func f2() {
panic("f2 测试个错误") //主动抛出异常,结束代码
fmt.Println("f2")
}
// panic错误
func main() {
f1()
f2()
fmt.Println("这是main函数")
}
定义捕获异常函数
func GoRecover() {
if err := recover(); err != nil {
return
}
}
// 使用方法
func demo() {
defer GoRecover()
// pass
}
包的导入和初始化函数init
package main
// 如果想把自己的go文件当做包导入,函数名、结构体、字段名、变量名的第一个字母必须大写,否则导入包时找不到数据
import (
"fmt"
// 包的路径是src目录下边的路径
m "Go学习/day5/demo1" // 给导入包做别名,使用方法 m.Add(10)
// 如果只导入,不适应包内部的数据时,可以使用匿名导入包
_ "包路径" // 下划线代表 匿名导入包
)
// init的执行顺序,先执行全局声明,再执行初始化操作,最后执行代码
func init(){
fmt.Println("包被初始化了")
}
func main() {
fmt.Println("执行代码")
}
结构体
结构体技巧
结构体可以用于map的key和value类型
type m struct {
age int
name string
}
func main() {
h := make(map[m]int) // 把m结构体用于map的key类型
h[m{1,"a1"}] = 1
fmt.Println(h[m{1,"a1"}]) // out:1
fmt.Println(h[m{5,"a1"}]) // out:0
}
方法
构造函数
继承,结构体嵌套
接口
空接口 interface{}
任何其他类型都实现了空接口,空接口可以 赋任何类型的值。类似python的object对象。
任意变量都可以存到空接口变量中。
ok语法断言接口类型
v, ok := a.(int)
if !ok {
fmt.Println(“…”)
}
fmt.Println(v)
类型断言
package main
import (
"fmt"
)
// 类型断言
func iFok(a interface{}) {
switch n := a.(type) {
case int:
fmt.Println("整数", n)
case string:
fmt.Println("字符串", n)
default:
fmt.Println(“…”)
}
}
// 定义一个值可以存放任何类型的map
func main() {
opmap := make(map[string]interface{})
opmap["姓名"] = "崔百一"
opmap["年龄"] = 26
opmap["布尔值"] = true
opmap["浮点"] = 1.20
fmt.Println(opmap)
iFok(opmap["年龄"])
iFok(opmap["姓名"])
}
反射
package main
import (
"fmt"
"reflect"
)
// 通过反射修改结构体
type Student struct {
Name string `json:"name" db:"my"`
Age int `json:"age" db:"3306"`
}
func main() {
// 通过反射获取指针类型,并修改值
var x float32 3.5
demo(&x)
fmt.Println(x)
// 通过反射修改结构体
var s Student
v := reflect.ValueOf(&s)
// Elem()获取指针指向的变量,相当于*s
v.Elem().Field(0).SetString("cby") // Field(下标)获取结构体下标字段的值
v.Elem().FieldByName("Age").SetInt(28) // FieldByName()获取结构体字段的值
fmt.Printf("%#v
", s)
// 获取结构体中tag信息
s1 := Struct{}
stag := reflect.TypeOf(s1)
field := stag.Field(0) // 如果是结构体指针,则使用stag.Elem().Field(0)
fmt.Println(field.Tag.Get("json"), field.Tag.Get("db"))
}
func demo(x Interface{}) {
v := reflect.ValueOf(x) // 获取值
// v := v.Type() // 获取类型,和reflect.TypeOf功能是一样的
k := v.Kind()
switch k {
case reflect.Int64:
fmt.Printf("x is int64 is:%d", v.Int())
case reflect.Struct:
fmt.Println("结构体")
for i:=0;i<v.NumField();i++ {
field := v.Field(i)
fmt.Printf("name:%s,type:%v,value:%v
", field.Name, field.Type().Kind(), field.Interface())
}
case reflect.Ptr:
fmt.Println("指针")
v.Elem().SetFloat(6.5)
default:
ftm.Println("...")
}
}
并发编程
goroutine
package main
import (
"fmt"
"sync"
)
// sync.WaitGroup 优雅的等待
var wg sync.WaitGroup
func hello() {
defer wg.Done() //计数器-1
fmt.Println("Hello")
}
func main() {
wg.Add(2) //计数器+2
go hello()
go hello()
fmt.Println("世界")
wg.Wait() //阻塞,一直等待所有goroutine结束
}
chan
并发原语和chan的应用场景
- 共享资源的并发访问使用传统并发原语;
- 复杂的任务编排和消息传递使用
Channel
; - 消息通知机制使用
Channel
,除非只想signal
一个goroutine
,才使用Cond
; - 简单等待所有任务的完成用
WaitGroup
,也有Channel
的推崇者用Channel
,都可以; - 需要和
Select
语句结合,使用Channel
; - 需要和超时配合时,使用
Channel
和Context
。
创建chan
并发:同一时间段同时在做多个事情
并行:同一时刻同时在做多个事情
c := make(chan int, 10)
有缓存是异步的,无缓存是同步阻塞的
这里有个缓冲,因此放入数据的操作c<- 0先于取数据操作 <-c
由于c是无缓冲的channel,因此必须保证取操作<-c 先于放操作c<- 0
取数据: <-c
放数据: c <- 1
有缓冲的channel,因此要注意“放”先于“取”
无缓冲的channel,因此要注意“取”先于“放”
接收chan
x, b := <-ch // 第一个值是返回chan中的元素,第二个值是bool类型(如果为false,chan已经被close而且chan中没有缓存的数据,此时第一个值是零值)
零值chan
未初始化的chan的零值是nil,是一种特殊的chan,对值是nil的chan的发送接收调用者总是会阻塞的。
清空chan
for range ch {
}
单向通道
func f0(ch chan int){...} // 既能接收也能发送
func f1(ch chan<- int){...} // 只能发送,往通道发送值
func f2(ch <-chan int){...} // 只能接收,从通道取值
关闭通道
如果 chan 为 nil,close 会 panic;如果 chan 已经 closed,再次 close 也会 panic。
只要一个 chan 还有未读的数据,即使把它 close 掉,你还是可以继续把这些未读的数据消费完,之后才是读取零值数据。
方法1,推荐的写法
func a(ch chan int) {
for i :=0; i<10; i++ {
ch <- i
}
close(ch) // 关闭管道
}
func main() {
ch := make(chan int)
go a(ch)
for v := range ch {
fmt.Println("data:", v)
}
}
方法2
v, ok := <-ch // ok是false,v为chan类型的零值
if ok == false {...}
chan常见错误
panic错误:
- close为nil的chan
- send已经close的chan
- close已经close的chan
同步原语
同步原语的适应场景
- 共享资源。并发地读写共享资源,会出现数据竞争(data race)的问题,所以需要 Mutex、RWMutex 这样的并发原语来保护。
- 任务编排。需要 goroutine 按照一定的规律执行,而 goroutine 之间有相互等待或者依赖的顺序关系,我们常常使用 WaitGroup 或者 Channel 来实现。
- 消息传递。信息交流以及不同的 goroutine 之间的线程安全的数据交流,常常使用 Channel 来实现。
检测并发访问共享资源的工具
go run -race counter.go #如果有问题,会输出警告信息
#开启了 race 的程序部署在线上,还是比较影响性能的。
go tool compile -race -S counter.go
#增加了 runtime.racefuncenter、runtime.raceread、runtime.racewrite、runtime.racefuncexit 等检测 data race 的方法。通过这些指令就能检测出data race问题。
Mutex(互斥锁)
适应场景:比如多个 goroutine 并发更新同一个资源,像计数器;同时更新用户的账户信息;秒杀系统;往同一个 buffer 中并发写入数据等等。如果没有互斥控制,就会出现一些异常情况,比如计数器的计数不准确、用户的账户可能出现透支、秒杀系统出现超卖、buffer 中的数据混乱等。
var lock sync.Mutex // 声明锁
lock.Lock() // 加锁
lock.Unlock() // 解锁
Mutex的其它用法
1.很多情况下,Mutex 会嵌入到其它 struct 中使用:在初始化嵌入的 struct 时,也不必初始化这个 Mutex 字段,不会因为没有初始化出现空指针或者是无法获取到锁的情况。
type Counter struct {
mu sync.Mutex
Count uint64
}
2.采用匿名字段的方式:
func main() {
var counter Counter
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
for j := 0; j < 100000; j++ {
counter.Lock()
counter.Count++
counter.Unlock()
}
}()
}
wg.Wait()
fmt.Println(counter.Count)
}
type Counter struct {
sync.Mutex
Count uint64
}
3.如果嵌入的 struct 有多个字段,我们一般会把 Mutex 放在要控制的字段上面,然后使用空格把字段分隔开来。即使你不这样做,代码也可以正常编译,只不过,用这种风格去写的话,逻辑会更清晰,也更易于维护。
// 线程安全的计数器类型
type Counter struct {
CounterType int
Name string
mu sync.Mutex
count uint64
}
// 加1的方法,内部使用互斥锁保护
func (c *Counter) Incr() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
4.实现一个线程安全的队列(通过slice实现的队列不是线程安全的,需要配合Mutex)
type SliceQueue struct {
data []interface{}
mu sync.Mutex
}
func NewSliceQueue(n int) (q *SliceQueue) {
return &SliceQueue{data: make([]interface{}, 0, n)}
}
// Enqueue 把值放在队尾
func (q *SliceQueue) Enqueue(v interface{}) {
q.mu.Lock()
q.data = append(q.data, v)
q.mu.Unlock()
}
// Dequeue 移去队头并返回
func (q *SliceQueue) Dequeue() interface{} {
q.mu.Lock()
if len(q.data) == 0 {
q.mu.Unlock()
return nil
}
v := q.data[0]
q.data = q.data[1:]
q.mu.Unlock()
return v
}
RWMutex(读写锁)
只有在读的操作多于写的操作时,使用读写锁可以提高性能。
var rwlock sync.RWMutex
rwlock.Lock() // 加写锁
rwlock.Unlock() // 解写锁
rwlock.RLock() // 加读锁
rwlock.RUnlock() // 解读锁
rwlock.RLocker() //
并发中使用map
Go语言中内置的map不是并发安全的,需使用sync.MAP
需要为map加锁来保证并发的安全性了,Go语言的sync包中提供了一个开箱即用的并发安全版map–sync.Map。开箱即用表示不用像内置的map一样使用make函数初始化就能直接使用。同时sync.Map内置了诸如Store、Load、LoadOrStore、Delete、Range等操作方法。
var m = sync.Map{}
m.Store(key, n) // 给map增加key和value,Store设置值,Load加载值,Range遍历map
atomic包
Context
作用
- 取消
goroute
任务 - 进行超时控制
- 传递通用参数
import (
"context"
"time"
)
ctx, cancel := context.WithCancel(context.Background())
// 1. 返回一个cancel函数,调用cancel函数的时候,会触发context.Done函数
// 2. 当执行一个后台任务时,怎么取消这个后台任务?
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50) // 绝对时间
context.WithDeadline // 相对时间
// 0. 通常用于数据库或者网络连接的超时控制
// 1. 超过指定时间之后,会触发context.Done函数
// 2. 当执行一个函数调用,特别是rpc调用时,怎么做超时控制?
// 传递上下文通用参数
context.WithValue(context.Background(), key, value) // 把参数设置到context中
// context.Value(key) // 获取参数
// 一个请求需要访问N个子系统,这时候如何跟踪各个子系统执行的情况呢?
func demo(ctx context.Context) {
for {
select {
case <- ctx.Done():
return
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
}
接口定义
context.Context
是一个接口,该接口定义了四个需要实现的方法。具体签名如下
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
-
Deadline
方法需要返回当前Context
被取消的时间,也就是完成工作的截止时间(deadline); -
Done
方法需要返回一个Channel
,这个Channel
会在当前工作完成或者上下文被取消之后关闭,多次调用Done
方法会返回同一个Channel
; -
Err
方法会返回当前Context
结束的原因,它只会在返回的
Channel
被关闭时才会返回非空的值;- 如果当前
Context
被取消就会返回Canceled
错误; - 如果当前
Context
超时就会返回DeadlineExceeded
错误;
- 如果当前
-
Value
方法会从Context
中返回键对应的值,对于同一个上下文来说,多次调用Value
并传入相同的Key
会返回相同的结果,该方法仅用于传递跨API和进程间跟请求域的数据;
常用库
sort
n := []int{3,1,5,2}
s := []string{"c","a","b"}
x := {5.2, -1.3, 0.7, -3.8, 2.6}
sort.Ints(n) // 排序int类型切片
sort.Sort(sort.Reverse(sort.IntSlice(n))) // 倒序排序int类型切片
sort.Strings(s) // 排序string类型切片
sort.Sort(sort.Reverse(sort.StringSlice(s))) // 倒序排序string类型切片
sort.Float64s(x) // 排序浮点数切片
http
import (
"fmt"
"io/ioutil"
"net/http"
"crypto/tls"
)
func test() {
// https请求跳过证书验证
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://www.baidu.com/test")
// resp.StatusCode 可以获取响应状态码
if err != nil {
fmt.Printf("get api failed, err:%v
", err)
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
}
POST请求
JsonData, err := json.Marshal(data) // data换成请求报文数据
if err != nil {
fmt.Printf("序列化json失败, err:%v
", err)
}
response, err := http.Post("http://www.api.com", "application/json", strings.NewReader(string(JsonData)))
if err != nil {
fmt.Printf("请求接口失败,err:%v
", err)
}
defer response.Body.Close()
resp, err := ioutil.ReadAll(response.Body)
if err != nil {
fmt.Printf("get response data failed, err:%v
", err)
}
fmt.Printf("请求接口成功response:%s
", string(resp))
delete 和 put 请求
res, err := http.NewRequest("DELETE", "http://www.api.com", nil) // 第三个参数如果有请求报文就换成请求报文
if err != nil {
fmt.Printf("请求失败, err:%v
", err)
}
// 添加header
res.Header.Add("content-type", "application/json")
response, _ := http.DefaultClient.Do(res)
defer response.Body.Close()
data, _ := ioutil.ReadAll(response.Body)
fmt.Printf("删除成功response:%s
", string(data))
time
package main
import (
"fmt"
"time"
)
// unix时间戳转换成时间
func mytime(timestamp int64) {
timeObj := time.Unix(timestamp, 0) //将时间戳转为时间格式
year := timeObj.Year() //年
month := timeObj.Month() //月
day := timeObj.Day() //日
hour := timeObj.Hour() //小时
minute := timeObj.Minute() //分钟
second := timeObj.Second() //秒
fmt.Printf("%4d-%02d-%02d %02d:%02d:%02d
", year, month, day, hour, minute, second)
}
// 将字符串时间转换为时间戳
func main() {
location, _ := time.LoadLocation("Asia/Shanghai")
TIME_LAYOUT := "2006-01-02 15:04"
t, err := time.ParseInLocation(TIME_LAYOUT, "2020-03-20 11:11", location)
if err != nil {
fmt.Println(err)
}
fmt.Println(t.Unix())
}
// 时间格式化
func formattime(){
now := time.Now()
fmt.Println(now.Format("2006-01-02-15-04"))
fmt.Println(now.Format("2006/01/02 15:04:05.000")) //05表示秒,000表示纳秒, 加上PM表示上下午
fmt.Println(now.Format("15:04 2006-01-02"))
fmt.Println(now.Format("01-02-15"))
}
// 获取2分钟之前的时间
t := now.Add(time.Second * -120)
// 时间格式化字符串
strconv.FormatInt(time.Now().Unix(), 10)
// 定义一个5秒执行一次的定时任务,Second秒,Minute分,Hour时
func tickDemo() {
ticker := time.Tick(time.Second * 5) //定义一个5秒间隔的定时器
for range ticker {
go func() {
fmt.Println("hello")
}()
}
}
func main() {
now := time.Now().Unix()
mytime(now)
formattime()
tickDemo()
}
json
// 解析第三方的api数据格式技巧
s := `{
"data": [
{
"synonym": "go",
"tag":"type1",
"name": "cby"
},
{
"synonym": "python",
"tag":"type2",
"name": "cby"
}
]
}`
// 方法1
m := make(map[string]interface{})
json.Unmarshal([]byte(s), &m)
fmt.Printf("%+v
", m["data"].([]interface{})[1]).(map[string]interface{})["synonym"] // 得有类型声明,告诉是什么类型
// 方法2(推荐),用结构体来解析想要获取的json数据值
m := struct {
Data []struct {
Synonym string `json:"synony"`
Tag string `json:"tag"`
} `json:"data"`
}{}
json.Unmarshal([]byte(s), &m)
fmt.Printf("%+v, %+v
", m.Data[1].Synonym, m.Data[1].Tag)
文件读操作
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
f, err := os.Open("input.dat")
if err != nil {
fmt.Println("err")
return
}
defer f.Close()
inputReader := bufio.NewReader(f) // 获取一个读取器变量
for {
// 带缓冲的
// buf := make([]byte, 1024)
// n, err := inputReader.Read(buf)
// if (n == 0) { break}
inputString, readerError := inputReader.ReadString('
')
fmt.Printf("The input was: %s", inputString)
if readerError == io.EOF {
return
}
}
}
文件写操作
/*
os.O_WRONLY 只写
os.O_CREATE 创建文件
os.O_RDONLY 只读
os.O_RDWR 读写
os.O_TRUNC 清空
os.O_APPEND 追加
*/
func demo1() {
file, err := os.OpenFile("xx.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("open file failed, err:", err)
return
}
defer file.Close()
str := "hello 沙河"
file.Write([]byte(str)) //写入字节切片数据
file.WriteString("hello") //直接写入字符串数据
}
func demo2() {
file, err := os.OpenFile("xx.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
fmt.Println("open file failed, err:", err)
return
}
defer file.Close()
writer := bufio.NewWriter(file)
for i := 0; i < 10; i++ {
writer.WriteString("hello
") //将数据先写入缓存
}
writer.Flush() //将缓存中的内容写入文件
}
正则表达式
package main
import (
"fmt"
"regexp"
)
var text = `my0 123@qq.com
my1 abc@qq.com
my2 qwe@163.com
`
func main() {
r := regexp.MustCompile(`([a-zA-Z0-9]+)@([a-zA-Z0-9]+)(.[a-zA-Z0-9.]+)`)
// FindAllStringSubmatch 方法获取正则匹配括号里的内容,返回二维切片[[x xx] [1 2]]
// 参数1: 需处理的数据, 参数2: -1表示获取全部匹配的字符串
data := r.FindAllStringSubmatch(text, -1)
// FindAllString 方法获取正则表达式匹配到的所有内容,返回string
// data := r.FindAllString(text, -1)
for _, d := range data {
fmt.Println(d[1])
}
}
gorm
package main
import (
// "fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
// 数据库表结构字段
type User struct {
Name string
Gender string
Age int
}
func main() {
db, err := gorm.Open("mysql", "cby:cby1234@(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local")
if err!= nil{
panic(err)
}
defer db.Close()
db.AutoMigrate(&User{})
u := User{"cby", "nam", 28}
// 创建记录
db.Create(&u)
// 执行原生sql,增,删,改
db.Exec("delete from test.users where name = 'name';")
}
filepath
Glob类似python的glob
package main
import (
"fmt"
"path/filepath"
)
func main() {
f := "/Users/cby/Desktop/Go学习/*/*.go"
s, err := filepath.Glob(f)
if err != nil{
return
}
for i := range s{
fmt.Println(s[i])
}
}