Go语言基础学习

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 语言中,只允许布尔型、字符串、数字类型这些基础类型作为常量。

  1. iota 枚举,只能在常量中使用,iota默认是0
  2. 遇到const iota就初始化为零
  3. const中每新增一行变量声明iota就递增1
  4. 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
}

切片

切片底层使用的数组
数组和切片的区别:

  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的应用场景

  1. 共享资源的并发访问使用传统并发原语;
  2. 复杂的任务编排和消息传递使用 Channel
  3. 消息通知机制使用 Channel,除非只想 signal 一个 goroutine,才使用 Cond
  4. 简单等待所有任务的完成用 WaitGroup,也有 Channel的推崇者用 Channel,都可以;
  5. 需要和 Select 语句结合,使用 Channel
  6. 需要和超时配合时,使用 ChannelContext

创建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错误:

  1. close为nil的chan
  2. send已经close的chan
  3. 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

作用

  1. 取消goroute任务
  2. 进行超时控制
  3. 传递通用参数
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])
   }
}
你好
原文地址:https://www.cnblogs.com/cuibaiyi/p/15450339.html