一、前言 此篇主要讲解go的通用语法和一些linux专属操作,windows专属操作可以看 go for windows
1. 环境 1 2 => go version go version go1.16.4 linux/amd64
2. 安装gvm版本管理工具 2.1. 安装 1 2 wget https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer bash gvm-installer
默认生效到~/.bashrc,生效到zsh需要将下面一句话加到.zshrc最后 1 [[ -s "/home/test/.gvm/scripts/gvm" ]] && source "/home/test/.gvm/scripts/gvm"
2.2. 使用 1 2 3 4 5 6 7 8 # 查看可安装的版本 gvm listall # 查看当前版本 gvm list # 安装一个版本 gvm install go1.18.3 # 使用一个版本,回归默认就是 gvm use system gvm use go1.18.3
二、语法相关 1. 基本类型 1.1. 去除变量未定义提示 1.2. int 1) 大小 32位下int为4个字节,64位下int为8个字节 2) 类型转换 1.2. 类型转换 float32 float64 1 2 3 var f float32 = 1 var a int = int (f)
[]byte 1 2 3 data := []byte ("aaa" ) str := string (data)
interface 类型判断
类型断言
1 2 3 4 5 var f interface {}if _, ok = f.(string ); !ok { fmt.Println("f type is not string" ) }
switch类型判断
1 2 3 4 5 6 7 8 9 10 11 12 13 var f interface {}switch f := f.(type ) {case string : fmt.Println("f type is string" ) f = "xxx" case int : fmt.Println("f type is int" ) default : fmt.Println("f type is unknown" ) }
1.2. string 1) 转换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var jsonStr string jsonByte := []byte (jsonStr) testByte := jsonStr[0 ] var num uint64 num, err := strconv.ParseUint("1234" , 10 , 64 ) var num uint64 num, err := strconv.ParseInt("1234" , 10 , 64 )
2) 单引号,双引号,反引号的区别 1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "fmt" ) func main () { fmt.Printf("%c %d\n" , 'a' , 'a' ) fmt.Printf("a\n" ) fmt.Printf("%s\n" , `a\n` ) }
输出
2) 字符串截取 1 2 3 4 5 6 7 outStr := "abcdefg" fmt.Println(outStr[0 ]) fmt.Println(outStr[:3 ]) fmt.Println(outStr[3 :5 ]) fmt.Println(outStr[5 :]) fmt.Println(outStr[:-2 ])
3) 遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 s := "abc" for i := range (s) { fmt.Println(i) } for i, v := range (s) { fmt.Println(i, byte (v)) }
4) 修改 string类型里面的元素是不可变的,除非赋值一个新的string 如果想要修改string中某一个元素,相当于拷贝了两次 1 2 3 4 5 6 a := "abc" tmp := []byte (a) tmp[0 ] = 'z' fmt.Println(a) a = string (tmp) fmt.Println(a)
2. map 2.1. 一些基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 xxxMap := make (map [string ]interface {}) check := map [byte ]byte { ')' : '(' , '[' : ']' , '{' : '}' , } if _, ok := xxxMap[key]; ok { } for key, value := range xxxMap { } for key := range xxxMap { }
2.2. map取不存在的值 对map取值可以获取一个返回值也可以获取两个返回值用于判断是否存在 如果不存在采用一个返回值会返回对应value的类型的默认值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { boolMap := map [int ]bool {} intMap := map [int ]int {} stringMap := map [int ]string {} sliceMap := map [int ][]int {} mapMap := map [int ]map [int ]int {} interfaceMap := map [int ]interface {}{} fmt.Println(boolMap[1 ]) fmt.Println(intMap[1 ]) fmt.Println(stringMap[1 ]) fmt.Println(sliceMap[1 ], sliceMap[1 ] == nil ) fmt.Println(mapMap[1 ], mapMap[1 ] == nil ) fmt.Println(interfaceMap[1 ]) }
2.3. 底层实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 type Map struct { Key *Type Elem *Type Bucket *Type Hmap *Type Hiter *Type } type hmap struct { count int flags uint8 B uint8 noverflow uint16 hash0 uint32 buckets unsafe.Pointer oldbuckets unsafe.Pointer nevacuate uintptr extra *mapextra } func NewMap (k, v *Type) *Type { t := newType(TMAP) mt := t.MapType() mt.Key = k mt.Elem = v if k.HasTParam() || v.HasTParam() { t.SetHasTParam(true ) } if k.HasShape() || v.HasShape() { t.SetHasShape(true ) } return t } func newType (et Kind) *Type { t := &Type{ kind: et, width: BADWIDTH, } t.underlying = t switch t.kind { case TMAP: t.extra = new (Map) ... } return t }
注意事项 var xxx map[string]interface{}定义的xxx是一个空指针,没有指向任何地址,不能进行赋值初始化map需要使用make 3. array 3.1. 一些基本操作 1 2 3 4 5 6 7 8 arr0 := [...]int {0 , 1 } arr1 := [...]int {0 , 1 } for _, v := range arr0 { fmt.Println(v) }
5. 流程控制语句 5.1. for 循环 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 for { ... } for i := 0 ; i < len (a); i++ { ... } for _, v := range (arr) { ... } for { ... if expression { break } } for { if expression { break } ... }
5.2. switch 1 2 3 4 5 6 7 8 9 switch v.key {case "Maker" : ... case "OS" : ... case "linuxOS" , "linuxKernel" , "linuxArch" : ... }
6. 函数 6.1. 定义和返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func test (a int ) int { return a + 1 } func test (a int ) (int , int ) { return a+1 , a-1 } func test (a int ) []int { return nil } func test (a int ) (result int ) { result = a + 1 return }
6.2. 值传递 (1) go函数都是值传递 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func changeDatas (intData int , strData string , arrData [3]int ) { intData += 2 strData += "bbb" arrData[0 ] = 999 } func main () { intData := 1 strData := "aaa" arrData := [...]int {1 , 2 , 3 } fmt.Println(intData, strData, arrData) changeDatas(intData, strData, arrData) fmt.Println(intData, strData, arrData) }
输出
1 2 1 aaa [1 2 3] 1 aaa [1 2 3]
(2) slice、map、channel类型的看似引用传递的解释 slice slice本质是一个结构体,内部包含三要素:长度、容量、数据首地址 所以传递的也是这三个值,数据内容改变改的是地址里面的内容,外部可见,但是改了长度和容量,外部就不会变 1 2 3 4 5 6 7 8 9 10 11 12 func changeDatas (sliceData []int , mapData map [string ]int ) { sliceData[0 ] = 999 mapData["test" ] = 123 } func main () { sliceData := []int {1 , 2 , 3 } mapData := make (map [string ]int ) fmt.Println(sliceData, mapData) changeDatas(sliceData, mapData) fmt.Println(sliceData, mapData) }
输出
1 2 [1 2 3] map[] [999 2 3] map[test:123]
map和channel都是指针 本质上map和channel都是指针,所以改动外部都可见 7. 组合赋值 组合赋值会先将值取出再赋值给前面的变量,所以go不需要swap方法 1 2 3 a, b := 1 +1 , 2 +2 a, b = b, a
8. i++、i– 9. const & iota 9.1. itoa 参考golang const 内itoa 用法详解及优劣分析
(1) 每次 const 出现时,都会让 iota 初始化为0 1 2 3 4 5 const a = iota const ( b = iota c )
(2) 自定义类型 自增长常量经常包含一个自定义枚举类型,允许你依靠编译器完成自增设置。
1 2 3 4 5 6 7 8 type Newtype int const ( T1 Newtype = iota T2 T3 T4 )
(3) 可跳过的值 1 2 3 4 5 6 7 8 9 10 type AudioOutput int const ( OutMute AudioOutput = iota OutMono OutStereo _ _ OutSurround )
(4) 位掩码表达式 1 2 3 4 5 6 7 8 9 type Allergen int const ( IgEggs Allergen = 1 << iota IgChocolate IgNuts IgStrawberries IgShellfish )
(5) 定义数量级 1 2 3 4 5 6 7 8 9 10 11 12 13 type ByteSize float64 const ( _ = iota KB ByteSize = 1 << (10 * iota ) MB GB TB PB EB ZB YB )
(6) 定义在一行的情况 跟普通形式 没什么不同 iota 在下一行增长,而不是立即取得它的引用。 1 2 3 4 5 6 7 8 9 10 11 const ( Apple, Banana = iota + 1 , iota + 2 Cherimoya, Durian Elderberry, Fig )
(7) 中间插队 中间插队时,iota 会被覆盖掉 不再继续自增。但是用另一个 iota 接一下,又会继续自增。 示例如下,中间插入了5、3和6,3下面有itoa接,6没有。
1 2 3 4 5 6 7 8 9 const ( a = iota b = 5 c = 3 d = iota e = 6 f g )
10. 并发 10.1. 基础概念 和线程的区别
OS线程(操作系统线程)一本都有固定的栈内存(通常为2MB) 一个 goroutine 的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine 的栈不是固定的,他可以按需增大和缩小,grorutine的栈大小限制可以达到1GB,但极少情况下会到1GB。所以在Go语言中一次创建十万左右的 grorutine 也是可以的。 和线程的关系
一个操作系统线程对应用户态多个goroutine。 go程序可以同时使用多个操作系统线程。 goroutine和OS线程是多对多的关系,即m:n。 其他
1.5版本之前,go仅占用一个核执行 1.5之后,默认使用所有核 10.2. sync.WaitGroup 等待退出 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "sync" ) var wg sync.WaitGroupfunc hello (i int ) { fmt.Println("Hello" , i) wg.Done() } func main () { fmt.Println("Hello world, main" ) for i := 1 ; i < 1000 ; i++ { wg.Add(1 ) go hello(i) } wg.Wait() }
10.3. runtime.GOMAXPROCS(i int) 限定占用核心数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport ( "fmt" "sync" "runtime" ) var wg sync.WaitGroupfunc hello (i int ) { fmt.Println("Hello" , i) wg.Done() } func main () { runtime.GOMAXPROCS(1 ) fmt.Println("Hello world, main" ) for i := 1 ; i < 1000 ; i++ { wg.Add(1 ) go hello(i) } wg.Wait() }
10.4. 互斥锁 11. 面向对象编程 11.1. 属性和方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport ( "fmt" ) type Rectangle struct { X int Y int } func (r *Rectangle) Area() int { return r.X * r.Y } func main () { rec := Rectangle{ X: 5 , Y: 10 , } fmt.Println(rec.Area()) }
11.2. 访问权限 首字母大小写可以控制访问权限 不过小写对整个package都可以访问,外部不可以访问 11.3. 继承 结构体里面直接定义另一个结构体就可以实现继承 但是初始化不能直接初始化父类的属性 同一个包内,大小写都可以访问到 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport "fmt" type Rectangle struct { x int Y int } func (r *Rectangle) Area() int { return r.x * r.Y } type testRec struct { Rectangle } func main () { rec := testRec{} rec.x = 10 rec.Y = 4 fmt.Println(rec.Area()) }
11.4. 多态 多态在go里面更多把思想转成接口 对外提供接口,但是存在不同实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Product interface { Use() } type productA struct { Product } type productB struct { productA }
11.5. 析构函数 go可以给类设置析构函数,但是析构时机是GC触发的时机 如果进程直接退出,由操作系统回收内存,不会触发析构函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" "runtime" ) type testT struct { a int next *testT } func main () { var root testT runtime.SetFinalizer(&root, func (tmp *testT) { fmt.Println("hhh" , tmp.a) }) }
11.6. 纯虚函数 对于go来说,不存在虚函数的概念,但是如果想要使用父类指针指向子类指针,必须实现父类定义的所有接口 接口类指向子类需要取地址,接口类可以直接调用自己的方法,会根据内存找到子类的方法调用 接口类指向子类内部只保存了子类的地址,所以参数传递可以值传递,改动会改动到内部子类的属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 type TestI interface { TestFunc() Print() } type TestImpl struct {}func (t *TestImpl) Print() { fmt.Println("TestImpl Print" ) } type TestImpl1 struct {}func Constructor () TestI { return &TestImpl1{} } func (t *TestImpl1) TestFunc() { fmt.Println("TestImpl1 TestFunc" ) } func (t *TestImpl1) Print() { fmt.Println("TestImpl1 Print" ) } func main () { a := &TestImpl{} a.Print() var b TestI = &TestImpl{} var c TestI = Constructor() var d *TestI = &c c.Print() d.Print() }
12. 指针 12.1. int转指针 go中限制了指针类型的转换,不允许将int转成指针类型 但是,转换一下想法,用二级指针进行赋值即可 1 2 3 4 var tmp **testTtmp1 := 0x111111111 tmp = (**testT)(unsafe.Pointer(&tmp1)) fmt.Printf("%p" , *tmp)
13. GC 垃圾回收机制 13.1. 内存管理 程序在内存上被分为堆区、栈区、全局数据区、代码段、数据区五个部分。对于C++等早期编程语言栈上的内存由编译器管理回收,堆上的内存空间需要编程人员负责申请与释放。在Go中栈上内存仍由编译器负责管理回收,而堆上的内存由编译器和垃圾收集器负责管理回收,给编程人员带来了极大的便利性。
13.2. GC触发时机 触发GC有俩个条件,一是堆内存的分配达到控制器计算的触发堆大小,初始大小环境变量GOGC,之后堆内存达到上一次垃圾收集的2倍时才会触发GC。二是如果一定时间内没有触发,就会触发新的循环,该触发条件由runtime.forcegcperiod变量控制,默认为2分钟。
13.3. GC带来的便利和坑 1) 便利 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import ( "fmt" "runtime" "time" "strconv" "unsafe" ) type testT struct { a int next *testT } func testFunc (root *testT) (arr []string ) { for i := 0 ; i < 5 ; i++ { var tmp testT tmp.a = i root.next = &tmp arr = append (arr, fmt.Sprintf("%p" , &tmp)) runtime.SetFinalizer(&tmp, func (tmp *testT) { fmt.Println("hhh" , tmp.a) }) root = root.next } return } func main () { var root testT arr := testFunc(&root) for i := 0 ; i < 10 ; i++ { time.Sleep(time.Second) runtime.GC() } for e := &root; e != nil ; e = e.next { fmt.Println(e.a) } for i := 0 ; i < 10 ; i++ { time.Sleep(time.Second) runtime.GC() } fmt.Println(root.a) }
14. struct 14.1. String() 默认打印方法 使用值传递实现的,打印原始和指针类型都可以输出 使用指针传递实现的,必须使用指针类型打印才能输出,值传递输出不出来 1 2 3 4 5 6 7 8 9 10 11 12 13 14 type A struct { name string } func (a A) String() string { return "A name: " + a.name } func main () { a := A{name: "a" } fmt.Println(a) }
14.2. 初始化父类属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type ProductA struct { Name string } type ProductB struct { ProductA } func main () { p := ProductB{ ProductA: ProductA{ Name: "ProductB" , }, } fmt.Println(p.Name) }
14.2. 选项与默认值 在Go语言中,结构体选项与默认值的设置有多种方式,下面介绍四种常用的方法:
1. 直接赋值方式 直接赋值是最简单的方式,需要外部调用方获取默认值然后修改。这种方式简单直观,但不够灵活。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 type Server struct { Host string Port int Timeout time.Duration MaxConn int } func DefaultServer () *Server { return &Server{ Host: "localhost" , Port: 8080 , Timeout: 30 * time.Second, MaxConn: 100 , } } func main () { server := DefaultServer() server.Host = "example.com" server.Port = 9090 fmt.Printf("%+v\n" , server) }
2. Builder方式 Builder方式使用链式调用,通过.WithXXX().WithXXX()方式设置选项。这种方式灵活且可读性好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 type Server struct { host string port int timeout time.Duration maxConn int } type ServerBuilder struct { server *Server } func NewServerBuilder () *ServerBuilder { return &ServerBuilder{ server: &Server{ host: "localhost" , port: 8080 , timeout: 30 * time.Second, maxConn: 100 , }, } } func (b *ServerBuilder) WithHost(host string ) *ServerBuilder { b.server.host = host return b } func (b *ServerBuilder) WithPort(port int ) *ServerBuilder { b.server.port = port return b } func (b *ServerBuilder) WithTimeout(timeout time.Duration) *ServerBuilder { b.server.timeout = timeout return b } func (b *ServerBuilder) WithMaxConn(maxConn int ) *ServerBuilder { b.server.maxConn = maxConn return b } func (b *ServerBuilder) Build() *Server { return b.server } func main () { server := NewServerBuilder(). WithHost("example.com" ). WithPort(9090 ). WithTimeout(60 * time.Second). Build() fmt.Printf("%+v\n" , server) }
3. 函数选项模式(Functional Options) 函数选项模式是Go中非常流行的一种方式,new方法内置默认值,然后传入变参opts,在new里面赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 type Server struct { host string port int timeout time.Duration maxConn int } type Option func (*Server) func WithHost (host string ) Option { return func (s *Server) { s.host = host } } func WithPort (port int ) Option { return func (s *Server) { s.port = port } } func WithTimeout (timeout time.Duration) Option { return func (s *Server) { s.timeout = timeout } } func WithMaxConn (maxConn int ) Option { return func (s *Server) { s.maxConn = maxConn } } func NewServer (opts ...Option) *Server { server := &Server{ host: "localhost" , port: 8080 , timeout: 30 * time.Second, maxConn: 100 , } for _, opt := range opts { opt(server) } return server } func main () { server := NewServer( WithHost("example.com" ), WithPort(9090 ), WithTimeout(60 * time.Second), WithMaxConn(200 ), ) fmt.Printf("%+v\n" , server) }
4. 接口选项模式(Interface Options) 接口选项模式是在函数选项模式的基础上,将opts类型从func变成interface,实现apply方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 type Server struct { host string port int timeout time.Duration maxConn int } type Option interface { apply(*Server) } type optionFunc func (*Server) func (f optionFunc) apply(s *Server) { f(s) } func WithHost (host string ) Option { return optionFunc(func (s *Server) { if host == "" { return } s.host = host }) } func WithPort (port int ) Option { return optionFunc(func (s *Server) { if port <= 0 || port > 65535 { return } s.port = port }) } func WithTimeout (timeout time.Duration) Option { return optionFunc(func (s *Server) { if timeout <= 0 { return } s.timeout = timeout }) } func WithMaxConn (maxConn int ) Option { return optionFunc(func (s *Server) { if maxConn <= 0 { return } s.maxConn = maxConn }) } func NewServer (opts ...Option) *Server { server := &Server{ host: "localhost" , port: 8080 , timeout: 30 * time.Second, maxConn: 100 , } for _, opt := range opts { opt.apply(server) } return server } func main () { server := NewServer( WithHost("example.com" ), WithPort(9090 ), WithTimeout(60 * time.Second), WithMaxConn(200 ), ) fmt.Printf("%+v\n" , server) }
对比函数选项式
这种方式对比函数选项式,限制了外部不能自己实现Option接口,只能使用内置的函数选项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 server := NewServer( WithHost("example.com" ), WithPort(9090 ), WithTimeout(60 * time.Second), WithMaxConn(200 ), func (s *Server) { s.Port = -1 }, ) server := NewServer( WithHost("example.com" ), WithPort(9090 ), WithTimeout(60 * time.Second), WithMaxConn(200 ), func (s *Server) { s.Port = -1 }, )
15. 常量 go中常量只能是数字、布尔、字符串,其他类型无法定义成常量 16. package 包 16.1. init函数 init函数不可被调用,是golang提供的引入包就会调用的一个初始化函数 对于package main来说也是一样的,会在main函数调用前进行调用 init可以定义多个,按照定义顺序执行 17. channel 17.1. 基本知识 天生的观察者消费者模式,支持多写多读 自带防止惊群效应的实现,读协程只会唤醒一个 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 var wg sync.WaitGroupvar wgW sync.WaitGroupfunc read (i int , ch chan int ) { time.Sleep(1 * time.Second) for a := range ch { fmt.Println(i, a) } wg.Done() } func write (i int , ch chan int ) { ch <- i fmt.Println(i, " write" ) wgW.Done() } func main () { ch := make (chan int , 100 ) for i := 0 ; i < 500 ; i++ { wg.Add(1 ) go read(i, ch) } for i := 0 ; i < 50 ; i++ { wgW.Add(1 ) go write(i, ch) } wgW.Wait() close (ch) fmt.Println("close channel" ) wg.Wait() }
17.2. close后的特性 close不会阻塞,关闭后可读不可写,写会崩溃 close后,正在阻塞的消费者可以拿到一个默认值,for的方式会直接退出循环 close后,正在阻塞的生产者会崩溃 1) close之后不能写,正在写的会崩溃 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func main () { ch := make (chan int ) go func () { fmt.Println("begin write" ) ch <- 1 fmt.Println("end write" ) }() time.Sleep(time.Second) close (ch) fmt.Println("close done" ) time.Sleep(time.Second) fmt.Println("main done" ) }
2) close之后可以读,把没读完的读完就退出了,for就读完退出,没数据直接读拿到默认值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 func main () { ch := make (chan int , 10 ) go func () { fmt.Println("begin write" ) ch <- 1 ch <- 2 ch <- 3 ch <- 4 ch <- 5 fmt.Println("end write" ) }() time.Sleep(time.Second) close (ch) fmt.Println("close done" ) for v := range ch { fmt.Println(v) } v := <-ch fmt.Println(v) }
17.3. 缓冲与无缓冲 无缓冲channel只负责数据的流转不负责存储数据,所以发送前必须有数据的接收者,否则发送会阻塞 缓冲channel可以储存部分数据,发送在缓冲区满之前可以发完继续运行 17.4. 只读和只写chan 只读和只写一般由一个可读可写的channel转化而来 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 func productor (wch chan <- int ) <-chan bool { quit := make (chan bool ) go func () { for i := 0 ; i < 10 ; i++ { fmt.Println("product" , i) wch <- i } quit <- true }() return quit } func consumer (rch <-chan int ) <-chan bool { quit := make (chan bool ) go func () { for v := range rch { fmt.Println("consumer" , v) time.Sleep(100 * time.Millisecond) } quit <- true }() return quit } func main () { rwCh := make (chan int , 10 ) qw := productor(rwCh) qr := consumer(rwCh) <-qw close (rwCh) <-qr }
17.5. 底层原理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 type hchan struct { qcount uint dataqsiz uint buf unsafe.Pointer elemsize uint16 closed uint32 elemtype *_type sendx uint recvx uint recvq waitq sendq waitq lock mutex } type Chan struct { Elem *Type Dir ChanDir } func NewChan (elem *Type, dir ChanDir) *Type { t := newType(TCHAN) ct := t.ChanType() ct.Elem = elem ct.Dir = dir if elem.HasTParam() { t.SetHasTParam(true ) } if elem.HasShape() { t.SetHasShape(true ) } return t } func newType (et Kind) *Type { t := &Type{ kind: et, width: BADWIDTH, } t.underlying = t switch t.kind { ... case TCHAN: t.extra = new (Chan) ... } return t }
底层也是指针,所以传参传入的是指针,不用担心被拷贝 18. slice 18.1. 一些基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 s0 := []int {0 , 1 } s1 := []int {0 , 1 } s2 := make ([]int , 5 ) s3 := make ([]int , 0 , 10 ) s0 = append (s0, 2 ) s0 = append (s0, 3 , 4 ) s0 = append (s0, s1...) s0 = s0[2 :] s1 = s1[:len (s1)-2 ] s0 = s0[:0 ] for _, v := range s0 { fmt.Println(v) }
18.2. 切片截取 1 2 3 4 5 6 7 8 testArr := []int {2 , 7 , 4 , 9 , 1 , 4 , 8 } fmt.Println(cap (testArr), testArr) testArr = testArr[:len (testArr)-1 ] fmt.Println(cap (testArr), testArr) testArr = testArr[1 :] fmt.Println(cap (testArr), testArr) testArr = testArr[:0 ] fmt.Println(cap (testArr), testArr)
可以认为切片的截取就是在原切片的基础上,返回了一个切片,data的首地址、len、cap分别改了一下,本质上复用了原始的地址空间 1 2 3 4 5 6 7 8 9 10 func main () { a := []int { 1 , 2 , 3 , 4 , 5 , } fmt.Println(&a[0 ], &a[1 ]) b := a[:3 ] fmt.Println(&b[0 ], &b[1 ]) b[0 ] = 3 fmt.Println(a) }
18.3. 动态初始化二维数组(矩阵) 矩阵的长宽不确定,构建的时候就需要一个一个进行构建,类似下面这样 1 2 3 4 seen := make ([][]bool , r) for i := 0 ; i < r; i++ { seen[i] = make ([]bool , c) }
18.4. 底层实现原理 底层数据结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 type slice struct { array unsafe.Pointer len int cap int } func NewSlice (elem *Type) *Type { if t := elem.cache.slice; t != nil { if t.Elem() != elem { base.Fatalf("elem mismatch" ) } if elem.HasTParam() != t.HasTParam() || elem.HasShape() != t.HasShape() { base.Fatalf("Incorrect HasTParam/HasShape flag for cached slice type" ) } return t } t := newType(TSLICE) t.extra = Slice{Elem: elem} elem.cache.slice = t if elem.HasTParam() { t.SetHasTParam(true ) } if elem.HasShape() { t.SetHasShape(true ) } return t }
扩容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 func growslice (oldPtr unsafe.Pointer, newLen, oldCap, num int , et *_type) slice { oldLen := newLen - num ... newcap := oldCap doublecap := newcap + newcap if newLen > doublecap { newcap = newLen } else { const threshold = 256 if oldCap < threshold { newcap = doublecap } else { for 0 < newcap && newcap < newLen { newcap += (newcap + 3 *threshold) / 4 } if newcap <= 0 { newcap = newLen } } } ... var p unsafe.Pointer if et.ptrdata == 0 { p = mallocgc(capmem, nil , false ) memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem) } else { p = mallocgc(capmem, et, true ) if lenmem > 0 && writeBarrier.enabled { bulkBarrierPreWriteSrcOnly(uintptr (p), uintptr (oldPtr), lenmem-et.size+et.ptrdata) } } memmove(p, oldPtr, lenmem) return slice{p, newLen, newcap} }
19. import 导入包 19.1. 普通导入 1 2 3 4 5 6 7 import ( "fmt" ) func main () { fmt.Println("Hello, world" ) }
19.2. 别名导入 1 2 3 4 5 6 7 import ( f "fmt" ) func main () { f.Println("Hello, world" ) }
19.3. 隐藏导入 1 2 3 4 5 6 import ( _ "fmt" ) func main () {}
19.4. 不需要使用包名字调用 1 2 3 4 5 6 7 8 import ( . "fmt" ) func main () { Println("Hello, world" ) }
20. 泛型 20.1. 泛型函数 1 2 func handle [T any ](l T) {}
20.2. 泛型的限制 泛型不能使用a.(type)获取类型,只能使用reflect.TypeOf()来获取 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func GetType [T any ](a T) { b := a.(int ) fmt.Println(b) switch a.(type ) { case int : fmt.Println("a is int" ) } b := reflect.TypeOf(a) fmt.Println("a is" , b) switch b.Kind() { case reflect.Int: fmt.Println("a is int" ) case reflect.Bool: fmt.Println("a is bool" ) } }
20.3. 自定义泛型类型 comparable 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func CompareSlice [T comparable ](l, r []T) bool { if len (l) != len (r) { return false } if len (l) == 0 { return true } for i := range l { if l[i] != r[i] { return false } } return true }
自定义其他类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type compareType interface { int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | string } func CompareUnorderSlice [T compareType ](l, r []T) bool { if len (l) != len (r) { return false } if len (l) == 0 { return true } a := DeepCopy(l) b := DeepCopy(r) sort.Slice(a, func (i, j int ) bool { return a[i] < a[j] }) sort.Slice(b, func (i, j int ) bool { return b[i] < b[j] }) return CompareSlice(a, b) }
衍生类型 1 2 3 4 5 6 7 8 9 10 11 func Add [T int ](a, b T) T { return a + b } type TestInt int func main () { var a TestInt = 1 var b TestInt = 2 fmt.Println(Add(a, b)) }
1 2 3 4 5 6 7 8 9 10 11 func Add [T ~int ](a, b T) T { return a + b } type TestInt int func main () { var a TestInt = 1 var b TestInt = 2 fmt.Println(Add(a, b)) }
官方定义的衍生类型 golang.org/x/exp/constraints 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 } type Unsigned interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr } type Integer interface { Signed | Unsigned } type Float interface { ~float32 | ~float64 } type Complex interface { ~complex64 | ~complex128 } type Ordered interface { Integer | Float | ~string }
21. defer 21.1. defer调用时机和顺序 defer是出函数作用域才调用,下面的调用是在main end之后 1 2 3 4 5 6 7 8 func main () { for i := 0 ; i < 5 ; i++ { defer func (i int ) { fmt.Println(i) }(i) } fmt.Println("main end" ) }
21.2. defer和命名返回值 命名返回值,就算没有显式赋值,defer拿到的也是最终的返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func aaa () int { return 5 } func testFunc () (a int ) { a = 4 defer func () { fmt.Println(a) }() return aaa() } func main () { testFunc() }
22. 作用域 22.1. 作用域变化的:= 作用域相同,不会新建同名变量 作用域不相同,同名变量会新建,外面的不会更改 1 2 3 4 5 6 7 8 9 10 11 12 func aaa () (int , int ) { return 5 , 6 } func main () { a := 4 defer func () { fmt.Println(a) }() a, b := aaa() fmt.Println(a, b) }
1 2 3 4 5 6 7 8 9 10 11 12 func aaa () (int , int ) { return 5 , 6 } func main () { a := 4 if true { a, b := aaa() fmt.Println(a, b) } fmt.Println(a) }
23. 跨平台 go可以在文件头部指定特定平台编译文件 具体支持的平台和架构使用go tool dist list查看 仅在linux的amd64下编译
在linux或mac下编译
24. 可变参数 指定类型,nums就是[]int
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func sum (nums ...int ) int { sum := 0 for _, num := range nums { sum += num } return sum } func main () { fmt.Println(sum(1 , 2 , 3 , 4 , 5 )) }
不指定类型,items就是[]interface{}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport "fmt" func print (items ...interface {}) { for _, item := range items { switch item.(type ) { case int : fmt.Println("int" , item) case string : fmt.Println("string" , item) case float64 : fmt.Println("float64" , item) case bool : fmt.Println("bool" , item) case rune : fmt.Println("rune" , item) case byte : fmt.Println("byte" , item) default : fmt.Println("unknown" , item) } } } func main () { print (1 , "2" , 3.0 , true , 'a' , byte ('b' ), []int {1 , 2 , 3 }) }
24.2. 三、GMP运行模型 参考一些好的帖子
1. 几个原则 G goroutine M machine 也是线程的抽象 P process 协程调度器 M必须绑定一个P才能执行 P默认是cpu核心数,可以使用runtime.GOMAXPROCS(2)设置P的数量 每个P上面有一个G的队列不超过256个,M绑定P后,从P的队列中取G运行 新的G创建出来后,先加入P的本地队列中,P的本地队列满了就取一半放到全局队列中 M把某个P上面的G队列执行完了,会从全局队列拿一批放到本地队列或者从其他P的本地队列偷一半到自己的本地队列中 如果G中某个系统调用阻塞了M,go底层hook了相关系统调用,会阻塞的将会把G和M绑定一起从P中排除,然后找空闲的M,没有空闲的M就直接新建一个M go程序启动会设置M的最大数量,默认10000. 但是内核很难支持这么多的线程数,所以这个限制可以忽略 可以手动设置M的最大数量,使用debug.SetMaxThreads(6)设置,但是超过此数量会直接崩溃而不是不建立继续运行 2. 调试方法 1 export GODEBUG=schedtrace=1000
1 SCHED 1001ms: gomaxprocs=16 idleprocs=15 threads=9 spinningthreads=0 needspinning=0 idlethreads=2 runqueue=0 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
SCHED:调试信息输出标志字符串,代表本行是 goroutine 调度器的输出;1001ms:即从程序启动到输出这行日志的时间;gomaxprocs=16: P的数量,默认的 P 的属性是和 cpu 核心数量默认一致idleprocs=15: 处于 idle 状态的 P 的数量;通过 gomaxprocs 和 idleprocs 的差值,我们就可知道执行 go 代码的 P 的数量;threads=9: os threads/M 的数量,包含 scheduler 使用的 m 数量,加上 runtime 自用的类似 sysmon 这样的 thread 的数量;spinningthreads=0: 处于自旋状态的 os thread 数量;needspinning=0: 需要自旋的 os thread 数量;idlethreads=2: 处于 idle 状态的 os thread 的数量;runqueue=0: Scheduler 全局队列中 G 的数量;[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]: 16个P的本地队列中的G的数量。四、标准库 1. encoding/json 1.1. 一些基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "encoding/json" ) func main () { json.Unmarshal(jsonBytes, &target) var f interface {} jsonBytes, err := json.Marshal(&f) jsonIndentBytes, err := json.MarshalIndent(&f, "" , " " ) }
1.2. key排序转json go默认对map[string]interface{}转json按照key升序转化 想要对key排序只能通过结构体的方式,转json会按照结构体的顺序转化 为空的key不想输出,可以加上omitempty 1 2 3 4 5 6 7 8 9 type outT struct { Type string `json:"type,omitempty"` Name string `json:"name,omitempty"` } func main () { var f outT str, _ := json.Marshal(&f) }
1.3. 单纯想压缩json不修改key顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { a := `{ "z": 1, "a": 2 }` var tmp interface {} json.Unmarshal([]byte (a), &tmp) out, _ := json.Marshal(tmp) fmt.Println(string (out)) out2 := &bytes.Buffer{} json.Compact(out2, []byte (a)) fmt.Println(out2.String()) }
注意事项 数字类型在Unmarshal之后会转成float64,只能断言为float64 2. testing 单测 单测文件要以xxx_test.go的格式,函数名以Testxxx的格式,package和要测试的文件属于同一个 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package serviceimport ( "encoding/json" "errors" "reflect" "sort" "testing" "github.com/gin-gonic/gin" . "github.com/agiledragon/gomonkey" . "github.com/smartystreets/goconvey/convey" ) func TestFunc (t *testing.T) { sess := session.Session{} var s *session.Session patch := ApplyMethod(reflect.TypeOf(s), "Commit" , func (_ *session.Session) error { return nil }) defer patch.Reset() Convey("TestFunc" , t, func () { Convey("正常流程" , func () { Convey("插入单个数据" , func () { sess.New() ... So(tmp, ShouldResemble, []string {"aaa" }) }) }) Convey("异常流程" , func () { Convey("GetSessionData出错" , func () { sess.New() ... patch1 := ApplyMethod(reflect.TypeOf(s), "GetSessionData" , func (_ *session.Session, _ string ) (interface {}, error ) { return nil , errors.New("testErr" ) }) ... So(tmp, ShouldResemble, []string {}) patch1.Reset() ... So(tmp, ShouldResemble, []string {"bbb" }) }) }) }) }
2.2. 性能分析 参考go 性能优化之 benchmark + pprof
1 2 3 func BenchmarkXXX (b *testing.B) { ... }
1 2 3 4 5 6 # -bench xxx 测试哪个函数 # -run none 不跑单测函数 # -benchmem 表示打印函数执行过程中的内存分配 # -cpuprofile xxx.out cpu文件路径 # -memprofile xxx.out 内存文件路径 go test -bench BenchmarkXXX -run none -benchmem -cpuprofile cpuprofile.out -memprofile memprofile.out [package]
正常结束后可以使用下面命令生成报告,会自动打开浏览器 1 go tool pprof -http=":8081" cpuprofile.out
2.3. 单测处理案例 1) http.ResponseWriter和http.Request构造 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 func handle (w http.ResponseWriter, r *http.Request) { var ( body []byte err error ) if r.Body != nil { body, err = io.ReadAll(r.Body) if err != nil { fmt.Println(err) return } } w.Write(body) } func main () { resp := httptest.NewRecorder() req := httptest.NewRequest("GET" , "/" , bytes.NewReader([]byte (`{"a":1}` ))) handle(resp, req) res := resp.Result() body, err := io.ReadAll(res.Body) if err != nil { fmt.Println(err) return } fmt.Println("body" , string (body)) }
2) *gin.Context构造 1 2 3 4 5 6 7 8 9 func getHTTPCtxWithGetVIPReq (reqBody []byte ) *gin.Context { gin.SetMode(gin.TestMode) resp = httptest.NewRecorder() ginCtx, _ := gin.CreateTestContext(resp) ginCtx.Request = httptest.NewRequest("GET" , "/" , bytes.NewReader(reqBody)) return ginCtx }
3. flag 控制台参数解析 3.1. 获取命令行参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" "flag" ) func main () { flag.Parse() fmt.Println(flag.Arg(0 )) fmt.Println(flag.Args()) fmt.Println(flag.NArg()) }
输出
1 2 3 4 => go run main.go a -a -b -asdfsaf cccc a [a -a -b -asdfsaf ccc] 5
4. path/filepath 目录操作库 4.1. 遍历目录下所有文件和目录,类似 find xxx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "flag" "fmt" "os" "path/filepath" ) func getFileList (path string ) { err := filepath.Walk(path, func (path string , f os.FileInfo, err error ) error { if f == nil { return err } if f.IsDir() { return nil } println (path) return nil }) if err != nil { fmt.Printf("filepath.Walk() return %v\n" , err) } } func main () { flag.Parse() var root string root = flag.Arg(0 ) getFileList(root) }
输出
1 2 3 4 5 6 7 8 9 10 11 12 => go run main.go . go.mod main.go => go run main.go ./ go.mod main.go => go run main.go ../ ../test/go.mod ../test/main.go => go run main.go /path/to/test /path/to/test/go.mod /path/to/test/main.go
4.2. 路径处理 1 2 3 4 5 func test () { filePath := "/home/test/aaaa/test.txt" fmt.Println(filepath.Base(filePath)) fmt.Println(filepath.Dir(filePath)) }
5. os/exec 命令行调用 5.1. 阻塞式等待返回,并输出stdout 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport ( "bytes" "fmt" "os/exec" ) func execShell (s string ) (string , error ) { cmd := exec.Command("/bin/bash" , "-c" , s) var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() if err != nil { fmt.Printf("%v\n" , err) } return out.String(), err } func main () { fmt.Println(execShell("ls /" )) }
6. net/http http网络请求库 6.1. 发送post请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "io/ioutil" "net/http" "strings" ) func main () { resp, err := http.Post("https://1.1.1.1:1234/a/b" , "application/x-www-form-urlencoded" , strings.NewReader("mobileId=!@#$%^" )) if err != nil { fmt.Println(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println(err) } fmt.Println(string (body)) }
6.2. 忽略https的证书错误 (1) 修改默认客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport ( "fmt" "io/ioutil" "net/http" "strings" "crypto/tls" ) func main () { http.DefaultClient.Transport = &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true , }, } resp, err := http.Post("https://1.1.1.1:1234/a/b" , "application/x-www-form-urlencoded" , strings.NewReader("mobileId=!@#$%^" )) ... }
6.3. 302不跳转 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main () { tr := http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true , }, } c := http.Client{ Transport: &tr, CheckRedirect: func (req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } resp, err := c.Get("https://10.240.17.108:8080" ) ... }
6.4. 获取tcp的四元组 第一个方式是transport的方式,自己处理连接的dail
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func main () { var conn net.Conn var connLock sync.Mutex tr := http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true , }, DialContext: func (ctx context.Context, network, addr string ) (net.Conn, error ) { var dialer net.Dialer c, err := dialer.DialContext(ctx, network, addr) if err == nil { connLock.Lock() conn = c connLock.Unlock() } return c, err }, } c := http.Client{ Transport: &tr } c.Get("https://10.240.17.108:8080" ) connLock.Lock() fmt.Println(conn.LocalAddr().String(), conn.RemoteAddr().String()) connLock.Unlock() }
第二个方式是使用trace插入到客户端中,对于底层改动基本无感
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import ( "context" "fmt" "net/http" "net/http/httptrace" "time" ) func main () { connStr := "empty" trace := &httptrace.ClientTrace{ GotConn: func (info httptrace.GotConnInfo) { connStr = fmt.Sprintf("%s => %s" , info.Conn.LocalAddr(), info.Conn.RemoteAddr()) }, } req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, "http://199.200.2.25:31080" , nil ) _, err := http.DefaultClient.Do(req) if err != nil { print ("error: %s" , err) return } fmt.Println(connStr) }
6.5. 作为服务端的信息获取 1 2 3 4 5 6 7 8 func print (r *http.Request) { fmt.Println("request host: " , r.Host) fmt.Println("request method: " , r.Method) fmt.Println("request header: " , r.Header) fmt.Println("request path: " , r.URL.Path) fmt.Println("request requestURI: " , r.RequestURI) fmt.Println("request remoteAddr: " , r.RemoteAddr) }
1 2 3 4 5 6 request host: 172.22.27.203:8081 request method: POST request header: map[Accept:[*/*] Accept-Encoding:[gzip, deflate, br] Cache-Control:[no-cache] Connection:[keep-alive] Content-Length:[38] Content-Type:[application/json] User-Agent:[PostmanRuntime-ApipostRuntime/1.1.0]] request path: /test/api/v1/client/aio/info/report request requestURI: /test/api/v1/client/aio/info/report?a=1 request remoteAddr: 172.22.27.203:58438
7. strings 字符串操作库 7.1. 字符串替换 1 2 3 4 5 6 7 8 9 10 11 package mainimport ( "strings" ) func main () { outStr := '{"a":"123"}' outStr = strings.ReplaceAll(outStr, `"` , `'` ) }
7.2. 裁剪两端字符串 1 2 outStr := "abffffffffabfffffffab" outStr = strings.Trim(outStr, "ab" )
7.3. 字符串分割 Split
1 arr := strings.Split("a,b,c" , "," )
Fields
1 arr := strings.Fields("a b c d" )
7.4. 字符串比较 Compare
1 2 3 if strings.Compare(event1[1 ], event2[0 ]) < 0 { }
8. encoding/hex 十六进制操作库 8.1. 将十六进制字符串转成数组 1 arr, err := hex.DecodeString("aabbcc" )
8.2. 将[]byte按照十六进制打印成字符串 1 hexStr := hex.EncodeToString([]byte ("123456" ))
9. io/ioutil io操作(到go1.16之后可以使用io或os库替代) 9.1. 文件读取 1 contentBytes, err := ioutil.ReadFile("/a/b/c.txt" )
10. os 系统操作 10.1. 目录操作 1) 基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 _, err := os.Stat("./" ) if err != nil && os.IsNotExist(err) { } execFilePath, _ := os.Executable() fmt.Println(execFilePath) os.TempDir() err := os.Chdir("/tmp" ) err := os.Mkdir(currentPath+"/tmp" , 0777 ) err := os.MkdirAll(currentPath+"/tmp/a/b" , 0777 ) err := os.Remove(currentPath + "/tmp" ) err := os.RemoveAll(currentPath + "/tmp" )
10.2. 文件操作 1) 整个文件读取 1 contentBytes, err := os.ReadFile("/a/b/c.txt" )
2) 按行读取 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func (s *defaultParser) ParseFile(filePath string , handler func (log string ) ) { f, err := os.Open(filePath) if err != nil { panic (err) } defer f.Close() rd := bufio.NewReader(f) for { line, err := rd.ReadString('\n' ) fmt.Println(line) if err != nil || io.EOF == err { break } } }
3) 写入文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { f, err := os.OpenFile(outpath, os.O_WRONLY|os.O_CREATE, 0644 ) if err != nil { panic (err) } defer f.Close() nw, err := f.Write(buf.Bytes()) if err != nil { panic (err) } if nw != buf.Len() { panic (fmt.Errorf("need to write %d, but write %d" , buf.Len(), nw)) } }
11. stronv 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "strconv" "fmt" ) func main () { f, _ := strconv.ParseFloat("1.234" , 64 ) fmt.Println(f) i, _ := strconv.ParseInt("123" , 0 , 64 ) fmt.Println(i) d, _ := strconv.ParseInt("0x1c8" , 0 , 64 ) fmt.Println(d) u, _ := strconv.ParseUint("789" , 0 , 64 ) fmt.Println(u) k, _ := strconv.Atoi("135" ) fmt.Println(k) _, e := strconv.Atoi("wat" ) fmt.Println(e) }
12. net 网络库 12.1. udp发送和接收 客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 package mainimport ( "flag" "fmt" "io" "log" "net" "os" ) func main () { flag.Parse() if flag.NArg() < 2 { log.Fatalln("Must input 2 params" ) } serverAddr := flag.Arg(0 ) filePath := flag.Arg(1 ) fmt.Printf("client, serverAddr: %s, filePath: %s\r\n" , serverAddr, filePath) conn, err := net.Dial("udp" , serverAddr) if err != nil { log.Fatalln(err) } defer conn.Close() f, err := os.Open(filePath) if err != nil { log.Fatalln(err) } defer f.Close() buf := make ([]byte , 50 *1024 ) count := 0 i := 0 for { n, err := f.Read(buf) if err != nil { if err == io.EOF { break } else { log.Fatalln(err) } } sendSize, err := conn.Write(buf[:n]) if err != nil { log.Fatalln(err) } count += sendSize msg := make ([]byte , 128 ) _, err = conn.Read(msg) if err != nil { log.Fatalln(err) } fmt.Printf("%d, Send %d, check %s\r\n" , i, n, string (msg)) i++ } fmt.Printf("Send %s to %s success, size %d" , serverAddr, filePath, count) }
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package mainimport ( "flag" "fmt" "log" "net" "os" ) func main () { flag.Parse() if flag.NArg() < 2 { log.Fatalln("Must input 2 params" ) } serverAddr := flag.Arg(0 ) filePath := flag.Arg(1 ) fmt.Printf("Hello, main, serverAddr: %s, filePath: %s\r\n" , serverAddr, filePath) f, err := os.Create(filePath) if err != nil { panic (err) } defer f.Close() addr, err := net.ResolveUDPAddr("udp" , serverAddr) if err != nil { log.Fatalln(err) } conn, err := net.ListenUDP("udp" , addr) if err != nil { log.Fatalln(err) } defer conn.Close() i := 0 for { data := make ([]byte , 100 *1024 ) n, rAddr, err := conn.ReadFromUDP(data) if err != nil { fmt.Println(err) continue } fmt.Printf("%d, Recieved from %s size %d\r\n" , i, rAddr.String(), n) f.Write(data[:n]) i++ _, err = conn.WriteToUDP([]byte (fmt.Sprintf("%d" , n)), rAddr) if err != nil { fmt.Println(err) continue } } }
12.2. tcp发送和接收 服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package mainimport ( "fmt" "net" ) func main () { ln, err := net.Listen("tcp" , ":8080" ) if err != nil { fmt.Println("Error listening:" , err) return } defer ln.Close() fmt.Println("Server started, waiting for connections..." ) for { conn, err := ln.Accept() if err != nil { fmt.Println("Error accepting connection:" , err) continue } go handleConnection(conn) } } func handleConnection (conn net.Conn) { defer conn.Close() buffer := make ([]byte , 1024 ) n, err := conn.Read(buffer) if err != nil { fmt.Println("Error reading:" , err) return } message := string (buffer[:n]) fmt.Printf("Received: %s" , message) response := fmt.Sprintf("Server received: %s" , message) conn.Write([]byte (response)) }
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport ( "bufio" "fmt" "net" "os" ) func main () { conn, err := net.Dial("tcp" , "localhost:8080" ) if err != nil { fmt.Println("Error connecting:" , err) return } defer conn.Close() fmt.Print("Enter message: " ) reader := bufio.NewReader(os.Stdin) text, _ := reader.ReadString('\n' ) fmt.Fprintf(conn, text+"\n" ) message, _ := bufio.NewReader(conn).ReadString('\n' ) fmt.Print("Server response: " + message) }
12.3. 给socket设置opt 1) 普通类型设置 2) 自定义结构体设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 func main () { ... dialer := &net.Dialer{ Control: func (network, address string , c syscall.RawConn) error { return c.Control(func (fd uintptr ) { type sockaddr struct { family uint16 port uint16 addr [4 ]byte sinZero [20 ]byte } addr := &sockaddr{ family: unix.AF_INET, addr: netip.MustParseAddr("1.2.3.4" ).As4(), } cmd := 0x248 _, _, e1 := unix.Syscall6(unix.SYS_SETSOCKOPT, fd, uintptr (unix.IPPROTO_IP), uintptr (cmd), uintptr (unsafe.Pointer(addr)), unsafe.Sizeof(*addr), 0 ) if e1 != 0 { panic (e1.Error()) } }) }, } conn, err := dialer.Dial("tcp" , addr) if err != nil { panic (err) } defer conn.Close() ... }
小技巧和踩坑记 1) tcp连接关闭直接发送rst包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 func handleConn (conn *net.TCPConn) { fmt.Println("send rst" ) if err := conn.SetLinger(0 ); err != nil { fmt.Println("Error setting linger:" , err) return } conn.Close() }
13. time 时间库 13.1. 基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "time" ) func main () { timestampS := time.Now().Unix() yestoday := time.Now().Add(-24 * time.Hour) du := time.Since(yestoday) du := time.Util(yestoday) }
13.2. 时钟说明 参考 go中使用单调时钟获得准确的时间间隔
time结构体记录了墙上时间和单调时间 使用time.Now()获取的会保存墙上时间和单调时间一起,计算Since的时候会使用单调时间进行计算 使用时间字符串得到的不会有单调时间,会随着系统时间变化而变化 14. sort 排序 14.1. 基本类型排序 1) 升序 1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "sort" ) func main () string { var queryKeyList []string var intList []int sort.Strings(queryKeyList) sort.Ints(intList) }
2) 降序 1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "fmt" "sort" ) func main () { testArr := []int {2 , 7 , 4 , 9 , 1 , 4 , 8 } sort.Sort(sort.Reverse(sort.IntSlice(testArr))) fmt.Println(testArr) }
解释一下上面的代码,sort.IntSlice(testArr)是将[]int强转成sort.IntSlice类型,类型定义了sort需要的三个函数,本质上还是[]int sort.IntSlice是sort.Interface的一个子类实现sort.Reverse(xxx)将传入的sort.Interface转成sort.reverse类型返回同样sort.reverse也是sort.Interface的一个子类实现,将Less()方法重写,反着调用原方法 sort.Sort()将sort.Interface进行排序14.2. 二分查找 1) 普通类型 sort包提供两个函数进行二分查找,并提供了一些基本类型的查找 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" "sort" ) func main () { a := []int {1 , 2 , 2 , 4 , 4 , 5 } fmt.Println(sort.Search(len (a), func (i int ) bool { return a[i] >= 2 })) fmt.Println(sort.Find(len (a), func (i int ) int { return 3 - a[i] })) fmt.Println(sort.SearchInts(a, 2 )) }
2) 字符串使用strings.Compare() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func main () { all := []string {"a" , "b" , "c" , "d" , "e" } sort.Slice(all, func (i, j int ) bool { return strings.Compare(all[i], all[j]) < 0 }) fmt.Println(all) fmt.Println(sort.Find(len (all), func (i int ) int { return strings.Compare("c" , all[i]) })) sort.Slice(all, func (i, j int ) bool { return strings.Compare(all[i], all[j]) > 0 }) fmt.Println(all) fmt.Println(sort.Find(len (all), func (i int ) int { return strings.Compare(all[i], "c" ) })) }
14.3. 任意类型的slice排序 1) 数字类型 直接使用sort.Slice排序,需要实现Less的函数 1 2 3 4 5 6 7 type item struct { val, count int } itemSlice := make ([]item, 10 ) ... sort.Slice(itemSlice, func (i, j int ) bool { return itemSlice[i].val < itemSlice[j].val })
2) 字符串类型 1 2 3 4 5 6 7 8 9 func main () { all := []string {"a" , "b" , "c" , "d" , "e" } sort.Slice(all, func (i, j int ) bool { return strings.Compare(all[i], all[j]) < 0 }) fmt.Println(all) }
14.4. 自定义类型排序 需要定义一个类型的三个接口,然后调用sort.Sort即可 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type NameSlice struct { names []string heights []int } func (x NameSlice) Len() int { return len (x.names) }func (x NameSlice) Less(i, j int ) bool { return x.heights[i] > x.heights[j] }func (x NameSlice) Swap(i, j int ) { x.heights[i], x.heights[j] = x.heights[j], x.heights[i] x.names[i], x.names[j] = x.names[j], x.names[i] } func sortPeople (names []string , heights []int ) []string { tmp := NameSlice{ names: names, heights: heights, } sort.Sort(tmp) return tmp.names }
14.5. sort内置类型 sort内置了几个基本类型的slice,实现了几个方法,可以直接用,如IntSlice 1 2 3 4 5 6 7 8 9 10 type IntSlice []int func (x IntSlice) Len() int { return len (x) }func (x IntSlice) Less(i, j int ) bool { return x[i] < x[j] }func (x IntSlice) Swap(i, j int ) { x[i], x[j] = x[j], x[i] }func (x IntSlice) Sort() { Sort(x) }
15. image 图像库 15.1. png处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package mainimport ( "bytes" "fmt" "image" "image/color" "image/png" "io/ioutil" "os" ) func main () { originByte, _ := ioutil.ReadFile("./test.png" ) originBuffer := bytes.NewBuffer(originByte) originImg, filename, err := image.Decode(originBuffer) if err != nil { return err.Error() } fmt.Println(filename) imgBounds := img.Bounds() imgX := imgBounds.Dx() imgY := imgBounds.Dy() firstRgba := img.At(0 , 0 ) r, g, b, a := firstRgba.RGBA() newImg := image.NewRGBA(image.Rectangle{ Min: image.Point{X: 0 , Y: 0 }, Max: image.Point{X: 100 , Y: 100 }, }) newImg.SetRGBA(5 , 5 , color.RGBA{ R: 0 , B: 0 , G: 0 , A: 255 , }) f, _ := os.Create("./output.png" ) png.Encode(f, newImg) defer f.Close() }
16. container/list 双向链表 value可以是任意类型的值 list本身不能拷贝,因为里面的element有一个指针指向root的地址,如果拷贝,root会被复制一份,将会导致element失效 需要将list放到结构体时,建议使用指针放置 16.1. 基本操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 tmp := list.New() tmp.PushBack(1 ) tmp.PushFront(3 ) for e := tmp.Front(); e != nil ; e = e.Next() { fmt.Println(e.Value) } for e := tmp.Front(); e != nil ; e = e.Next() { if e.Value == 3 { tmp.Remove(e) break } } for e := tmp.Front(); e != nil ; e = e.Next() { if e.Value == 3 { tmp.InsertAfter(1 , e) tmp.InsertBefore(2 , e) break } }
踩坑记 1) list放到结构体里面一定要注意结构体拷贝的问题,建议在结构体里面使用指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type test struct { head list.List } func main () { tmp := test{} tmp.head.PushFront(1 ) tmp2 := tmp tmp2.head.InsertAfter(2 , tmp2.head.Front()) for e := tmp2.head.Front(); e != nil ; e = e.Next() { fmt.Println(e.Value) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 type Element struct { next, prev *Element list *List Value any } ... type List struct { root Element len int } ... func (l *List) InsertAfter(v any, mark *Element) *Element { if mark.list != l { return nil } return l.insertValue(v, mark) }
上面的Element里面有一个变量list指向root的地址,也就是下面InsertAfter判断的值 当进行了拷贝之后,root本身是拷贝的,地址处于新的地址上,里面的元素的list还指向拷贝前的地址,自然和当前不相等,就会插入失败 解决办法就是使用指针,这样拷贝的是指针,里面的内容不会变化 上面的写法只是实例,实际场景经常会出现在构造函数构造完元素后返回了一个拷贝,如下面的错误写法 1 2 3 4 5 6 7 8 9 10 type Allocator struct { memMap list.List } func Constructor (n int ) Allocator { res := Allocator{} res.memMap.PushFront(1 ) return res }
17. runtime 一些运行需要的变量和方法 17.1. 常用变量 1) runtime.GOOS 操作系统类型 1 2 3 4 5 6 7 8 9 10 11 12 func test () { switch runtime.GOOS { case "windows" : fmt.Println("windows" ) case "linux" : fmt.Println("linux" ) case "darwin" : fmt.Println("mac" ) default : log.Fatalln("unsupported os" , runtime.GOOS) } }
18. io io相关操作 19. regexp 正则库 19.1. 提取字符串 括号内的是要提取的字符串,输出为整体匹配到的字符串加括号匹配的字符串 1 2 3 4 5 6 7 8 9 10 11 func TestRegexDefault (t *testing.T) { line := "[2022-12-26 18:49:24.631][ 25160: 6372][ info][aaa][bbb]testhhh\r\n" defaultRegexStr := `^\[(20[0-9]{2}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3})\]\[[ ]*(\w+):[ ]*(\w+)\]\[[ ]*(\w+)\]\[(\w+)\]\[[^\]]*\](.*)` matchRegex := regexp.MustCompile(defaultRegexStr) params := matchRegex.FindStringSubmatch(line) if len (params) == 0 { t.Fatal(defaultRegexStr, " parse failed, params " , params) } fmt.Println(params) }
20. archive/zip zip相关操作 20.1. 解压缩 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func unzip (zipFile string , destDir string ) error { zipReader, err := zip.OpenReader(zipFile) if err != nil { return err } defer zipReader.Close() for _, f := range zipReader.File { fpath := filepath.Join(destDir, f.Name) if f.FileInfo().IsDir() { os.MkdirAll(fpath, os.ModePerm) } else { if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { return err } inFile, err := f.Open() if err != nil { return err } defer inFile.Close() outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { return err } defer outFile.Close() _, err = io.Copy(outFile, inFile) if err != nil { return err } } } return nil }
20.2. 压缩 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 func Zip (srcFile string , destZip string ) error { zipfile, err := os.Create(destZip) if err != nil { return err } defer zipfile.Close() archive := zip.NewWriter(zipfile) defer archive.Close() filepath.Walk(srcFile, func (path string , info os.FileInfo, err error ) error { if err != nil { return err } header, err := zip.FileInfoHeader(info) if err != nil { return err } header.Name = strings.TrimPrefix(path, filepath.Dir(srcFile) + "/" ) if info.IsDir() { header.Name += "/" } else { header.Method = zip.Deflate } writer, err := archive.CreateHeader(header) if err != nil { return err } if ! info.IsDir() { file, err := os.Open(path) if err != nil { return err } defer file.Close() _, err = io.Copy(writer, file) } return err }) return err }
21. exec 执行命令 21.1. 打开浏览器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func openBrowser (url string ) { command := "" args := []string {} switch runtime.GOOS { case "windows" : command = "start" args = append (args, "/c" ) args = append (args, "start" ) case "linux" : command = "xdg-open" case "darwin" : command = "open" } args = append (args, url) fmt.Println("exec" , command, args) cmd := exec.Command(command, args...) cmd.Start() }
22. heap 堆 22.1. 小根堆 基础类型小根堆直接使用sort.IntSlice等可以实现 1 2 3 4 5 6 7 8 9 10 11 type lhp struct { sort.IntSlice }func (l *lhp) Push(x interface {}) { l.IntSlice = append (l.IntSlice, x.(int )) } func (l *lhp) Pop() interface {} { n := len (l.IntSlice) x := l.IntSlice[n-1 ] l.IntSlice = l.IntSlice[:n-1 ] return x }
上面代码大致解释就是,使用hp继承sort.IntSlice的各种基础方法 实现Push和Pop两个接口即可实现最小堆 22.2. 大根堆 照抄小根堆,然后将Less重写一下就好了,j和i互调顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 type bhp struct { sort.IntSlice }func (l *bhp) Less(i, j int ) bool { return l.IntSlice.Less(j, i) } func (l *bhp) Push(x interface {}) { l.IntSlice = append (l.IntSlice, x.(int )) } func (l *bhp) Pop() interface {} { n := len (l.IntSlice) x := l.IntSlice[n-1 ] l.IntSlice = l.IntSlice[:n-1 ] return x }
23. context 23.1. 关闭子协程的方式 1) 手动关闭协程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func main () { fatherCtx := context.Background() childCtx, cancelChild := context.WithCancel(fatherCtx) var wg sync.WaitGroup wg.Add(1 ) go func () { for { select { case <-childCtx.Done(): fmt.Println("exit because:" , context.Cause(childCtx)) wg.Done() return case <-time.After(1 * time.Millisecond): fmt.Println("begin do something" ) time.Sleep(5 * time.Second) } } }() time.Sleep(1 * time.Second) cancelChild() fmt.Println("notify child quit done" ) wg.Wait() }
2) 定点关闭协程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func main () { fatherCtx := context.Background() childCtx, cancelChild := context.WithDeadline(fatherCtx, time.Now().Add(3 *time.Second)) defer cancelChild() var wg sync.WaitGroup wg.Add(1 ) go func () { for { select { case <-childCtx.Done(): fmt.Println("exit because:" , context.Cause(childCtx)) wg.Done() return case <-time.After(1 * time.Second): fmt.Println("reach 1 s" ) } } }() fmt.Println("begin wait" ) wg.Wait() }
3) 定时关闭协程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func main () { fatherCtx := context.Background() childCtx, cancelChild := context.WithTimeout(fatherCtx, 3 *time.Second) defer cancelChild() var wg sync.WaitGroup wg.Add(1 ) go func () { for { select { case <-childCtx.Done(): fmt.Println("exit because:" , context.Cause(childCtx)) wg.Done() return case <-time.After(1 * time.Second): fmt.Println("reach 1 s" ) } } }() fmt.Println("begin wait" ) wg.Wait() }
4) 带原因的取消 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func main () { fatherCtx := context.Background() childCtx, cancelChild := context.WithCancelCause(fatherCtx) var wg sync.WaitGroup wg.Add(1 ) go func () { for { select { case <-childCtx.Done(): fmt.Println("exit because:" , context.Cause(childCtx)) wg.Done() return case <-time.After(1 * time.Second): fmt.Println("reach 1 s" ) } } }() cancelChild(fmt.Errorf("father notify" )) wg.Wait() }
23.2. 携带上下文 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func main () { fatherCtx := context.Background() childCtx := context.WithValue(fatherCtx, "test" , "123" ) child1Ctx, cancel := context.WithTimeout(childCtx, 3 *time.Second) defer cancel() var wg sync.WaitGroup wg.Add(1 ) go func () { for { select { case <-child1Ctx.Done(): fmt.Println("exit because:" , context.Cause(child1Ctx)) wg.Done() return case <-time.After(1 * time.Second): fmt.Println("reach 1 s, get test: " , child1Ctx.Value("test" )) } } }() fmt.Println("begin wait" ) wg.Wait() }
24. os/signal 信号处理库 24.1. 捕获信号 1 2 3 4 5 6 7 func main () { c := make (chan os.Signal, 10 ) signal.Notify(c, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) for s := range c { fmt.Println("recieve signal" , s) } }
操作 信号值 描述 ctrl+csyscall.SIGINT 中断 ctrl+zsyscall.SIGTSTP 停止 fg或bgsyscall.SIGCONT 继续
25. fmt 格式化输出库 25.1. Sprintf 格式化字符串 1) 自定义顺序格式化参数 1 2 3 func main () { fmt.Println(fmt.Sprintf("%[2]d %[1]d %[1]d %[2]d" , 10 , 20 )) }
26. math/bits 二进制相关 26.1. 统计二进制中1的个数 1 2 3 4 5 6 7 func main () { fmt.Println(bits.OnesCount(1 )) fmt.Println(bits.OnesCount(2 )) fmt.Println(bits.OnesCount(3 )) fmt.Println(bits.OnesCount(4 )) fmt.Println(bits.OnesCount(5 )) }
26.1. 计算数字的二进制需要多少位来表示 1 2 3 4 5 6 7 8 func main () { fmt.Println(bits.Len(0 )) fmt.Println(bits.Len(1 )) fmt.Println(bits.Len(2 )) fmt.Println(bits.Len(3 )) fmt.Println(bits.Len(4 )) fmt.Println(bits.Len(5 )) }
五、cgo 1. 多平台编译 1.1. 平台不同的flag 2. pkg-config 自定义动态库路径 将 CGO 与 Pkg-Config 和 自定义动态库位置一起使用
六、go命令相关 1. go test 执行单测用例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # # 执行当前目录下所有单测文件 go test # 执行当前目录下及子目录的所有单测文件 go test ./... # 执行package的某个子目录单测文件 go test [package]/[dir] # # 执行特定函数 go test -run ^testFunc$ # 执行多个特定函数 go test -run ^(testFunc|testFunc1)$ # go test -timeout 30s # # 默认成功不会打印fmt.Println等,加上-v就会打印 go test -v # 取消cache,默认会将已经执行过且为改过代码的package直接输出结果 go test -count=1
2. go build 编译二进制 go build不区分平台,可以在任意系统上编译其他大多数系统的版本 2.1. windows 32bit和64bit 1 2 3 4 5 6 7 8 9 10 11 12 13 # 32位编译 go env -w GOOS=windows go env -w GOARCH=386 go build go env -u GOARCH go env -u GOOS # 64位编译 go env -w GOOS=windows go env -w GOARCH=amd64 go build go env -u GOARCH go env -u GOOS
2.2. 全平台编译makefile示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 TARGET_NAME = testaaa BUILD_CMD = go build OUTPUT = $(TARGET_NAME) _x86.exe $(TARGET_NAME) _x64.exe $(TARGET_NAME) _linux_amd64 $(TARGET_NAME) _linux_arm64 $(TARGET_NAME) _mac_amd64 $(TARGET_NAME) _mac_arm64 all: $(OUTPUT) clean: rm -f $(OUTPUT) $(TARGET_NAME) _x86.exe: *.go go env -w GOOS=windows go env -w GOARCH=386 $(BUILD_CMD) -o $@ go env -u GOOS go env -u GOARCH $(TARGET_NAME) _x64.exe: *.go go env -w GOOS=windows go env -w GOARCH=amd64 $(BUILD_CMD) -o $@ go env -u GOOS go env -u GOARCH $(TARGET_NAME) _linux_amd64: *.go go env -w GOOS=linux go env -w GOARCH=amd64 $(BUILD_CMD) -o $@ go env -u GOOS go env -u GOARCH $(TARGET_NAME) _linux_arm64: *.go go env -w GOOS=linux go env -w GOARCH=arm64 $(BUILD_CMD) -o $@ go env -u GOOS go env -u GOARCH $(TARGET_NAME) _mac_amd64: *.go go env -w GOOS=darwin go env -w GOARCH=arm64 $(BUILD_CMD) -o $@ go env -u GOOS go env -u GOARCH $(TARGET_NAME) _mac_arm64: *.go go env -w GOOS=darwin go env -w GOARCH=arm64 $(BUILD_CMD) -o $@ go env -u GOOS go env -u GOARCH
2.3. 编译给c调用的动态库 代码上
必须有package main和main函数,但是可以什么都不做 必须包含import "C" 要导出的函数必须加上//export funcName,export前没有空格 导出函数本身无所谓首字母大小写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package mainimport "fmt" import "C" type Rectangle struct { x int y int } func (r *Rectangle) Area() int { return r.x * r.y } func test () { rec := Rectangle{ x: 14 , y: 34 , } fmt.Println(rec.Area()) } func main () {}
编译
1 2 3 4 # 要先安装标准库,会生成目录到 /usr/lib/go/pkg/linux_[arch ]_dynlink/ go install -buildmode=shared -linkshared std # 编译C动态库 go build -buildmode=c-shared -linkshared -o libtest.so
结果
会在当前目录生成一个静态库文件(libtest.so)一个头文件(libtest.h) strip后查看libtest.so,函数已经导出,依赖上述生成的目录下的libstd.so 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 => strip libtest.so => readelf -Ws libtest.so Symbol table '.dynsym' contains 33 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@GLIBC_2.17 (2) 3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable 4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 5: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.17 (2) 6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND runtime.morestack_noctxt 7: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND type.int 8: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND type.*os.File 9: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND os.Stdout 10: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND type.io.Writer 11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND runtime.addmoduledata 12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND os.(*File).Write 13: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND go.link.abihash.libstd.so 14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND runtime.unreachableMethod 15: 0000000000000000 0 FUNC GLOBAL DEFAULT UND runtime.convT64 16: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _rt0_arm64_linux_lib 17: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND runtime.lastmoduledatap 18: 0000000000000000 0 TLS GLOBAL DEFAULT UND runtime.tlsg 19: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fmt.Fprintln 20: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND __stack_chk_guard@GLIBC_2.17 (3) 21: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _cgo_wait_runtime_init_done 22: 0000000000000000 0 FUNC GLOBAL DEFAULT UND crosscall2 23: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _cgo_release_context 24: 000000000001f950 8 OBJECT GLOBAL DEFAULT 19 _rt0_arm64_linux_lib.ptr 25: 0000000000020238 0 NOTYPE GLOBAL DEFAULT 18 _edata 26: 0000000000020239 0 NOTYPE GLOBAL DEFAULT 18 _end 27: 0000000000001270 64 FUNC GLOBAL DEFAULT 13 _cgoexp_f70364e750d1_test 28: 00000000000012b0 144 FUNC GLOBAL DEFAULT 13 main.test 29: 0000000000001350 128 FUNC GLOBAL DEFAULT 13 test 30: 0000000000020238 0 NOTYPE GLOBAL DEFAULT 18 __bss_start 31: 000000000001f988 32 OBJECT GLOBAL DEFAULT 20 go.itab.*os.File,io.Writer 32: 000000000001f970 24 OBJECT GLOBAL DEFAULT 20 runtime.textsectionmap # 可以看到依赖了go自己的标准库和libc的库 => readelf -d libtest.so Dynamic section at offset 0xfce8 contains 32 entries: Tag Type Name/Value 0x0000000000000003 (PLTGOT) 0x1ff88 0x0000000000000002 (PLTRELSZ) 264 (bytes) 0x0000000000000017 (JMPREL) 0xf90 0x0000000000000014 (PLTREL) RELA 0x0000000000000007 (RELA) 0x948 0x0000000000000008 (RELASZ) 1608 (bytes) 0x0000000000000009 (RELAENT) 24 (bytes) 0x000000006ffffff9 (RELACOUNT) 55 0x0000000000000006 (SYMTAB) 0x288 0x000000000000000b (SYMENT) 24 (bytes) 0x0000000000000005 (STRTAB) 0x5a0 0x000000000000000a (STRSZ) 700 (bytes) 0x000000006ffffef5 (GNU_HASH) 0x860 0x0000000000000001 (NEEDED) Shared library: [libstd.so] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x0000000000000001 (NEEDED) Shared library: [ld-linux-aarch64.so.1] 0x000000000000000c (INIT) 0x1098 0x000000000000000d (FINI) 0x13d0 0x000000000000001a (FINI_ARRAY) 0x1f940 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) 0x0000000000000019 (INIT_ARRAY) 0x1f948 0x000000000000001b (INIT_ARRAYSZ) 24 (bytes) 0x000000000000001d (RUNPATH) Library runpath: [/usr/lib/go/pkg/linux_arm64_dynlink/] 0x0000000000000010 (SYMBOLIC) 0x0 0x000000000000001e (FLAGS) SYMBOLIC BIND_NOW 0x000000006ffffffb (FLAGS_1) Flags: NOW NODELETE 0x000000006ffffff0 (VERSYM) 0x8a8 0x000000006ffffffc (VERDEF) 0x8ec 0x000000006ffffffd (VERDEFNUM) 1 0x000000006ffffffe (VERNEED) 0x908 0x000000006fffffff (VERNEEDNUM) 2 0x0000000000000000 (NULL) 0x0
3. go env 设置环境变量 1 2 3 4 # 设置环境变量 go env -w xxx=xx # 恢复环境变量到默认值 go env -u xxx
实例
可以写到makefile中,跨平台的makefile 1 2 3 4 main.exe: main.go go env -w GOOS=windows go build go env -u GOOS
4. go install 安装全局包 1 go install -v github.com/cweill/gotests/gotests@v1.6.0
4.1. 常用工具链接 gotest: github.com/cweill/gotests/gotests go-outline: github.com/ramya-rao-a/go-outline gopls: golang.org/x/tools/gopls dlv: github.com/go-delve/delve/cmd/dlv 5. go generate 执行命令 在go文件中定义的命令,调用go generate时会执行 需要在main.go文件中如下定义,注释和go之间不能有空格,会执行generate后面的命令 一般用于生成语言文件或错误码信息等可以用命令生成的文件 6. dlv 调试go程序 1 go install -v github.com/go-delve/delve/cmd/dlv@latest
6.1. dlv监听远程调试 1 2 3 4 # 监听6666端口,挂载16703进程号 # --api-version 设置version为2,默认是1,vscode的客户端会使用2 # --headless=true 只启动server调试,不清楚什么是headless,但是都加了。。。 dlv --listen=:6666 --headless=true --api-version=2 attach 16703
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { "version" : "0.2.0" , "configurations" : [ { "name" : "Connect to server" , "type" : "go" , "request" : "attach" , "mode" : "remote" , "remotePath" : "${workspaceFolder}" , "port" : 6666 , "host" : "172.22.231.55" } ] }
7. go.work 大工程开发利器 vscode只能当go.mod在根目录的情况下才能进行提示,而在大工程情况下,go.mod可能不止一个。使用go.work之后,可以解决这个问题
1 2 3 4 # 初始化go.work go work init # 添加一个目录 go work use path/to/project
五、框架 1. 工程知识 1.1. go.mod go.mod是go工程用来管理包使用的,类似于nodejs的package.json文件1) 创建新工程 1. 先生成go.mod文件
1 2 # 初始化一个test 工程 go mod init test
2) go mod 一些基本操作 1 2 3 4 # 添加工程依赖包,会加入到go.mod文件中的require中 go mod download github.com/mattn/go-sqlite3 # 根据go.mod文件处理依赖关系 go mod tidy
(1) vendor 1 2 # 同步依赖包到当前的vendor目录 go mod vendor
2. goframe 2.1. 安装配置 安装gf命令行工具
1 2 # 下载安装gf wget -O gf https://github.com/gogf/gf-cli/releases/download/v1.16.3/gf_linux_amd64 && chmod +x gf && ./gf install
默认gf命令行工具不支持sqlite3,如果需要,上面安装完之后,自己编译一版sqlite3的gf工具 确保有gcc编译环境 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # clone 仓库git clone https://github.com/gogf/gf-cli && cd gf-cli # 设置环境变量 export CGO_ENABLED=1 # 将代码中的sqlite3依赖取消注释 vim command/gen/gen_dao.go ... # 执行gf的编译命令,需要上述步骤先下载gf工具 # -a指定平台,可以编译下面几种平台 # darwin amd64,arm64 # freebsd 386,amd64,arm # linux 386,amd64,arm,arm64,ppc64,ppc64le,mips,mipsle,mips64,mips64le # netbsd 386,amd64,arm # openbsd 386,amd64,arm # windows 386,amd64 gf build main.go -a amd64 -s linux --cgo # 安装 cp bin/linux_amd64/gf /usr/local/bin/gf
初始化
1 go mod download github.com/mattn/go-sqlite3
代码生成
根据config.toml生成数据库代码,这个比较方便 1 gf gen dao -c config/config.toml
2.2. 开发知识点 2.2.1. orm时间维护功能 orm的数据库更新操作会自动更新updatedAt、createdAt、deletedAt等字段 不想要更新字段需要加上Unscoped() 1 db.Table("user" ).Unscoped().Data(g.Map{"name" : "john guo" }).Where("name" , "john" ).Update()
2.2.2. 前后端字段对应 gf中使用下面的方法可以将前端传入参数解析到struct的对应成员中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type TaskApiCreateReq struct { Content string `json:"content" v:"required|length:1,100#任务内容不能为空|任务内容应当在:min到:max之间"` TaskType int `json:"taskType"` } func (a *task) Create(r *ghttp.Request) { var ( data *model.TaskApiCreateReq ) if err := r.Parse(&data); err != nil { response.JsonExit(r, 1 , err.Error()) } ... }
解析规则,会把前端传入的字段去除除了字母和数字的所有特殊字符,然后不区分大小写比较 下面的几个key都可以解析到上面的data.Content中 1 2 3 4 5 6 { "Content" : "xxx" , "content" : "xxx" , "con_tent" : "xxx" , "conTent" : "xxx" }
六、好用的第三方库 1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "github.com/google/uuid" ) func main () { id := uuid.New() fmt.Printf("%s %s\n" , id, id.Version().String()) }
输出
1 ab8d63ef-f0c4-a01d-a6ef-5f2c3efff5af VERSION_4
github.com/tuotoo/qrcode没成功的,这个成功了1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "bytes" "fmt" "image" "image/png" "io/ioutil" "github.com/makiuchi-d/gozxing" _ "github.com/makiuchi-d/gozxing/common" "github.com/makiuchi-d/gozxing/qrcode" ) func parseQrcode (pngPath string ) string { originByte, _ := ioutil.ReadFile(pngPath) originBuffer := bytes.NewBuffer(originByte) originImg, _, err := image.Decode(originBuffer) if err != nil { return err.Error() } fmt.Println(filename) bmp, _ := gozxing.NewBinaryBitmapFromImage(originImg) qrReader := qrcode.NewQRCodeReader() result, err := qrReader.Decode(bmp, nil ) if err != nil { return err.Error() } return result.GetText() }
3. golang.org/x/mobile go开发android和ios gomobile使用笔记
4.1. 获取进程信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func getProcessDir (name string ) string { pids, _ := process.Pids() for _, pid := range pids { p, _ := process.NewProcess(int32 (pid)) tmp, _ := p.Name() if tmp != name { continue } fmt.Println(p.Name()) fmt.Println(p.Exe()) fmt.Println(p.Cwd()) fmt.Println(p.Cmdline()) fmt.Println(p.Uids()) fmt.Println(p.Gids()) fmt.Println(p.Username()) fmt.Println(p.CreateTime()) fmt.Println(p.MemoryInfo()) fmt.Println(p.MemoryInfoEx()) fmt.Println(p.NumCtxSwitches()) fmt.Println(p.NumFDs()) fmt.Println(p.NumThreads()) fmt.Println(p.OpenFiles()) fmt.Println(p.Connections()) fmt.Println(p.IsRunning()) break } return "" }
5.1. 获取域名特定类型的解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func resolveDomain (domain string , server string , dnsType uint16 ) (r *dns.Msg, err error ) { cli := dns.Client{ Timeout: 5 * time.Second, } m := dns.Msg{} m.SetQuestion(dns.Fqdn(domain), dnsType) m.Id = dns.Id() m.RecursionDesired = false r, _, err = cli.Exchange(&m, server+":53" ) if err != nil { return } fmt.Println(r.String()) }
6. golang.org/x/text/encoding/simplifiedchinese 中文编码转换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import "golang.org/x/text/encoding/simplifiedchinese" func ConvertStr2GBK (str string ) string { ret, err := simplifiedchinese.GBK.NewEncoder().String(str) return ret b, err := simplifiedchinese.GBK.NewEncoder().Bytes([]byte (str)) return string (b) } func ConvertGBK2Str (gbkStr string ) string { ret, err := simplifiedchinese.GBK.NewDecoder().String(gbkStr) return ret b, err := simplifiedchinese.GBK.NewDecoder().Bytes([]byte (gbkStr)) return string (b) }
7.1. 构造ip包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import ( "github.com/google/gopacket" "github.com/google/gopacket/layers" ) func main () { const ( IPv4 = "ip4" Addr = "199.200.2.170" Port = 12345 ) dstIP, _ := net.ResolveIPAddr(IPv4, Addr) dstPort := layers.TCPPort(Port) sIP, _ := net.ResolveIPAddr(IPv4, "2.0.0.1" ) srcPort := layers.TCPPort(12345 ) fmt.Printf("Local IP:PORT - %s:%d \n" , sIP, srcPort) ipPack := &layers.IPv4{ Version: 4 , SrcIP: sIP.IP, DstIP: dstIP.IP, TTL: 64 , Protocol: layers.IPProtocolTCP, } tcpPack := &layers.TCP{ SrcPort: srcPort, DstPort: dstPort, Seq: 1205014776 , SYN: true , } if err := tcpPack.SetNetworkLayerForChecksum(ipPack); err != nil { log.Fatalln(err) } serialBuf := gopacket.NewSerializeBuffer() serialOpts := gopacket.SerializeOptions{ ComputeChecksums: true , FixLengths: true , } if err := gopacket.SerializeLayers(serialBuf, serialOpts, ipPack, tcpPack); err != nil { log.Fatalln(err) } fmt.Println(serialBuf.Bytes()) }
8.1. 构造tun设备读写数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 func main () { config := water.Config{ DeviceType: water.TUN, PlatformSpecificParams: water.PlatformSpecificParams{ Name: "tun123" , }, } ifce, err := water.New(config) if err != nil { log.Fatal(err) } fmt.Println("create tun" , ifce.Name()) cmd := exec.Command("ifconfig" , ifce.Name(), "up" ) if err := cmd.Run(); err != nil { log.Fatal(err) } go func () { buf := make ([]byte , 1500 ) for { n, err := ifce.Read(buf) if err != nil { log.Fatal(err) } fmt.Printf("Received packet: % x\n" , buf[:n]) } }() ... fmt.Println(serialBuf.Bytes()) _, err = ifce.Write(serialBuf.Bytes()) if err != nil { fmt.Println("Error sending packet:" , err) return } time.Sleep(1000 * time.Second) }
9.1. 一般用法 1 2 3 4 5 6 7 8 9 10 11 12 func TestConvey (t *testing.T) { Convey("测试1加上别的数" , t, func () { Convey("1+1=2" , func () { sum := 1 + 1 So(sum, ShouldEqual, 2 ) }) Convey("1+2=3" , func () { sum := 1 + 2 So(sum, ShouldEqual, 3 ) }) }) }
9.2. convey执行顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func TestConvey (t *testing.T) { Convey("测试1加上别的数" , t, func () { i := 1 fmt.Println() fmt.Print("set i to " , i) Convey("1+1=2" , func () { i += 1 So(i, ShouldEqual, 2 ) }) Convey("1+2=3" , func () { i += 2 So(i, ShouldEqual, 3 ) }) fmt.Println() fmt.Println("test done" ) }) }
1 2 3 4 5 6 7 8 9 10 === RUN TestConvey 测试1加上别的数 set i to 1 1+1=2 ✔ test done set i to 1 1+2=3 ✔ test done
从上述代码可以得出结论,每一个Convey的出现,都是把父节点的函数复制了一份,只执行当前的Convey,兄弟Convey不执行 所以每个父节点有几个子节点,其所在函数都会执行多少遍,每一遍只有一个Convey生效,其他的都是空 上面代码可以理解为 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 func TestConvey (t *testing.T) { func () { i := 1 fmt.Println() fmt.Print("set i to " , i) func () { i += 1 So(i, ShouldEqual, 2 ) }() fmt.Println() fmt.Println("test done" ) }() func () { i := 1 fmt.Println() fmt.Print("set i to " , i) func () { i += 2 So(i, ShouldEqual, 3 ) }() fmt.Println() fmt.Println("test done" ) }() }
测试集的实际效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 func TestConvey (t *testing.T) { Convey("测试1加上别的数" , t, func () { tests := []struct { name string add int want int }{ { name: "1+1=2" , add: 1 , want: 2 , }, { name: "1+2=3" , add: 2 , want: 3 , }, { name: "1+3=4" , add: 3 , want: 4 , }, } for i, tt := range tests { fmt.Println() fmt.Print("in " , i, ", tt " , tt) Convey(tt.name, func () { So(1 +tt.add, ShouldEqual, tt.want) }) fmt.Println() fmt.Print("out " , i, ", tt " , tt) } fmt.Println() }) }
输出如下,可以看出,循环在每个测试点都重新跑了一遍 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 === RUN TestConvey 测试1加上别的数 in 0, tt {1+1=2 1 2} 1+1=2 ✔ out 0, tt {1+1=2 1 2} in 1, tt {1+2=3 2 3} out 1, tt {1+2=3 2 3} in 2, tt {1+3=4 3 4} out 2, tt {1+3=4 3 4} in 0, tt {1+1=2 1 2} out 0, tt {1+1=2 1 2} in 1, tt {1+2=3 2 3} 1+2=3 ✔ out 1, tt {1+2=3 2 3} in 2, tt {1+3=4 3 4} out 2, tt {1+3=4 3 4} in 0, tt {1+1=2 1 2} out 0, tt {1+1=2 1 2} in 1, tt {1+2=3 2 3} out 1, tt {1+2=3 2 3} in 2, tt {1+3=4 3 4} 1+3=4 ✔ out 2, tt {1+3=4 3 4}
10. 国际化库 1) 准备工作 在根目录建立locals目录,里面只能放json文件 文件名只能是语言名称且只能是golang.org/x/text/language包中可以Make出来的名称 1 2 3 locales ├── en-US.json └── zh-CN.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" "golang.org/x/text/language" ) func main () { name := "zh-CN" tag := language.Make(name) if tag.String() != name { fmt.Println("can't use language package" , name) return } fmt.Println("language package" , name ,"is ok" ) }
1 2 3 4 5 6 7 func main () { fmt.Println(language.SimplifiedChinese.String()) fmt.Println(language.Chinese.String()) fmt.Println(language.TraditionalChinese.String()) fmt.Println(language.English.String()) fmt.Println(language.AmericanEnglish.String()) }
1 2 3 4 5 { "hello" : "hello world" , "test" : "test" , "testFormat" : "test %d" }
1 go install github.com/mylukin/easy-i18n/easyi18n@latest
1 easyi18n generate --pkg=catalog ./locales ./catalog/catalog.go
1 2 3 import ( _ "xtunnel/catalog" )
2) 使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" _ "test/catalog" "github.com/mylukin/easy-i18n/i18n" "golang.org/x/text/language" ) func main () { p := i18n.NewPrinter("zh-CN" ) p.Printf("testFormat\r\n" , 1 ) fmt.Println(p.Sprintf("hello" ), p.Sprintf("test" )) }
1) 语言文件分离放置 在项目根目录新建i18n目录,按照语言名称新建不同的目录,名字没有限制 i18n是默认的目录名,也可以自定义,自定义则在gi18n.New()设置到Options中就好里面可以放很多个文件,根据不同模块命名即可,文件可以是toml、yml、json、xml等 目录结构如下 1 2 3 4 5 6 7 8 9 10 i18n ├── en-US │ ├── test1.toml │ └── test2.toml ├── hhhhh │ ├── test1.toml │ └── test2.toml └── zh-CN ├── test1.toml └── test2.toml
1 2 3 test1 = "测试1" testFormat = "测试格式 %d"
使用如下,需要设置语言到ctx中,然后直接翻译成字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "context" "fmt" "github.com/gogf/gf/i18n/gi18n" ) func main () { g := gi18n.New() ctx := gi18n.WithLanguage(context.TODO(), "hhhhh" ) fmt.Println(g.T(ctx, "test1" )) fmt.Println(g.Tf(ctx, "testFormat" , 1 )) }
2) 语言文件打包到二进制 1 2 3 4 # 直接打包保留i18n的目录 gf pack -y i18n catalog/catalog.go # 将locales下文件打包到i18n目录(对于二进制来说) gf pack -y locales -p=i18n catalog/catalog.go
然后对应的go文件直接包含即可 不过go自己会找当前工程目录下的i18n目录(绝对路径),所以要测试可以重命名此目录进行测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport ( "context" "fmt" _ "test/catalog" "github.com/gogf/gf/i18n/gi18n" ) func main () { g := gi18n.New() ctx := gi18n.WithLanguage(context.TODO(), "hhhhh" ) fmt.Println(g.T(ctx, "test1" )) fmt.Println(g.Tf(ctx, "testFormat" , 1 )) }
11.1 使用 踩坑 1) 直接go test不生效,vscode调试可以 原因是直接跑编译器会自动优化代码,而此库不能对代码做优化,所以需要加-gcflags "all=-l" 在vscode中点击run test则需要添加下面的配置到settings.json中 1 2 3 4 { ... "go.testFlags" : [ "-gcflags=all=-l" ] }
12.1. 使用示例 1) 测试连接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "github.com/go-redis/redis/v7" ) func main () { redisCli := redis.NewClient(&redis.Options{ Addr: "localhost:6379" , DB: 0 , }) pong, err := redisCli.Ping().Result() if err != nil { fmt.Println("reis 连接失败:" , pong, err) return } fmt.Println("reis 连接成功:" , pong) }
2) 扫描key 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func main () { ... var ( cursor uint64 = 0 keys []string ) for { keys, cursor, err = redisCli.Scan(cursor, "SESS#*" , 1000 ).Result() if err != nil { fmt.Println(err) break } fmt.Println(keys) if cursor == 0 { break } } fmt.Println("redis 扫描结束" ) }
3) 增删改 1 2 3 4 5 6 7 8 9 10 11 12 func main () { ... err := redisCli.Set(key, value, 0 ).Err() if err != nil { panic (err) } if err := redisCli.Del(key).Err(); err != nil { fmt.Println("del key" , key, "get err" , err) } }
13. golang.org/x/sync/semaphore 信号量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package mainimport ( "context" "fmt" "math/rand" "sync" "time" "golang.org/x/sync/semaphore" ) func main () { var lock sync.RWMutex coMap := make (map [int ]bool ) s := semaphore.NewWeighted(10 ) for i := 0 ; i < 100 ; i++ { go func (id int ) { for { err := s.Acquire(context.Background(), 1 ) if err != nil { fmt.Println(err) time.Sleep(time.Second) continue } lock.Lock() coMap[id] = true lock.Unlock() time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000 ))) lock.Lock() delete (coMap, id) lock.Unlock() s.Release(1 ) } }(i) } for { time.Sleep(time.Second) lock.RLock() fmt.Println("currnet" , coMap) lock.RUnlock() } }
七、实战 1. 搭建简单http服务器 1.1. 模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package mainimport ( "encoding/json" "fmt" "log" "net/http" ) type retJson struct { Code int `json:"code"` LLLL string `json:"llll"` AAAA string `json:"aaaa"` Token string `json:"token"` } func smsServerHandler (w http.ResponseWriter, r *http.Request) { r.ParseForm() fmt.Println(r.Form) fmt.Println("path" , r.URL.Path) fmt.Println("scheme" , r.URL.Scheme) var ret retJson ret.Code = 0 ret.LLLL = "test" ret.AAAA = "stest" ret.Token = "a454as1bbvd5g5s15155" retBytes, _ := json.MarshalIndent(&ret, "" , " " ) retStr := string (retBytes) fmt.Println(retStr) w.Header().Set("Content-Type" , "application-json" ) fmt.Fprintf(w, retStr) } func main () { http.HandleFunc("/" , smsServerHandler) http.Handle("/" , http.FileServer(http.Dir("template" ))) err := http.ListenAndServe(":7878" , nil ) if err != nil { log.Fatal("ListenAndServe failed, err: " , err) } }
1.2. 静态页面路径返回 最简单跟目录返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "log" "net/http" ) func main () { http.Handle("/" , http.FileServer(http.Dir("template" ))) err := http.ListenAndServe(":7878" , nil ) if err != nil { log.Fatal("ListenAndServe failed, err: " , err) } }
其他路径返回页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" ) func rootHandler (w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { http.Redirect(w, r, "/template/" , http.StatusFound) return } w.Write([]byte (r.URL.Path)) } func main () { http.Handle("/template/" , http.StripPrefix("/template/" , http.FileServer(http.Dir("./template" )))) http.HandleFunc("/" , rootHandler) log.Printf("start server: http://localhost:4567 " ) err := http.ListenAndServe(":4567" , nil ) if err != nil { log.Fatal("Listen port failed, err: " , err) } }
1.3. 上传文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package mainimport ( "fmt" "io" "log" "net/http" "os" ) func uploadHandler (w http.ResponseWriter, r *http.Request) { file, handler, err := r.FormFile("file" ) if err != nil { fmt.Println("upload failed, err:" , err) w.Write([]byte ("upload success" )) return } defer file.Close() f, err := os.Create("./upload_dir/" + handler.Filename) if err != nil { panic (err) } defer f.Close() _, err = io.Copy(f, file) if err != nil { panic (err) } w.Write([]byte ("upload success" )) } func main () { http.Handle("/" , http.FileServer(http.Dir("template" ))) http.HandleFunc("/uploadfile" , uploadHandler) err := http.ListenAndServe(":4567" , nil ) if err != nil { log.Fatal("Listen port failed, err: " , err) } }
1.4. 302跳转 简单跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" ) func main () { http.HandleFunc("/" , http.RedirectHandler("http://1.1.1.1" , http.StatusFound)) log.Printf("start server: http://localhost:4567 " ) err := http.ListenAndServe(":4567" , nil ) if err != nil { log.Fatal("Listen port failed, err: " , err) } }
特定目录跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" ) func rootHandler (w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { http.Redirect(w, r, "/template/" , http.StatusFound) return } w.Write([]byte (r.URL.Path)) } func main () { http.HandleFunc("/" , rootHandler) log.Printf("start server: http://localhost:4567 " ) err := http.ListenAndServe(":4567" , nil ) if err != nil { log.Fatal("Listen port failed, err: " , err) } }
1.5. 代理转发 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport ( "crypto/tls" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "net/http/httputil" "net/url" ) func rootHandler (w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { http.Redirect(w, r, "/template/" , http.StatusFound) return } remote, err := url.Parse("https://1.1.1.1" ) if err != nil { log.Fatalln("serverAddr is invalid" ) } proxy := httputil.NewSingleHostReverseProxy(remote) tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true }, } proxy.Transport = tr proxy.ServeHTTP(w, r) } func main () { http.HandleFunc("/" , rootHandler) log.Printf("start server: http://localhost:4567 " ) err := http.ListenAndServe(":4567" , nil ) if err != nil { log.Fatal("Listen port failed, err: " , err) } }
1.4. 处理post的body数据 json数据存在body里面r.Body只能读取一次,读完就会关闭,需要再次读取需要加上下面注释说明的代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package mainimport ( "crypto/tls" "encoding/json" "fmt" "io/ioutil" "io" "log" "net/http" "net/http/httputil" "net/url" ) func rootHandler (w http.ResponseWriter, r *http.Request) { var body []byte if r.Body != nil { body, err = io.ReadAll(r.Body) if err != nil { fmt.Println(err) return } } r.Body = ioutil.NopCloser(bytes.NewReader(body)) } func main () { http.HandleFunc("/" , rootHandler) log.Printf("start server: http://localhost:4567 " ) err := http.ListenAndServe(":4567" , nil ) if err != nil { log.Fatal("Listen port failed, err: " , err) } }
八、算法和数据结构的实现 1. 数据结构 1.1. 队列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func main () { var test list.List test.PushBack(1 ) test.PushBack(2 ) value := test.Front().Value test.Remove(test.Front()) fmt.Println(value) fmt.Println(test.Len()) for iter := test.Front(); iter != nil ; iter = iter.Next() { fmt.Println(iter.Value) } }
1.2. 栈 1) list.List实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main () { var test list.List test.PushFront(1 ) test.PushFront(2 ) test.PushFront(3 ) value := test.Front().Value test.Remove(test.Front()) fmt.Println(value) fmt.Println(test.Len()) for iter := test.Front(); iter != nil ; iter = iter.Next() { fmt.Println(iter.Value) } }
2) slice实现 slice实现有个好处就是,入栈会动态扩容,出栈容量会保留 slice实现队列会导致重复内存申请 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main () { test := []int {} test = append (test, 1 ) test = append (test, 2 ) test = append (test, 3 ) value := test[len (test)-1 ] test = test[:len (test)-1 ] fmt.Println(value) fmt.Println(len (test)) for i := len (test); i > 0 ; i-- { fmt.Println(test[i-1 ]) } }
1.3. 链表 1.4. 大(小)根堆 官方库有heap,可以进行此操作,但是需要自己实现几个接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 type bhp struct { sort.IntSlice }func (l *bhp) Less(i, j int ) bool { return l.IntSlice.Less(j, i) } func (l *bhp) Push(x interface {}) { l.IntSlice = append (l.IntSlice, x.(int )) } func (l *bhp) Pop() interface {} { n := len (l.IntSlice) x := l.IntSlice[n-1 ] l.IntSlice = l.IntSlice[:n-1 ] return x } func main () { unorderList := []int {12 , 10 , 4 , 2 , 5 , 23 , 45 , 3 , 9 , 2 } fmt.Println(unorderList) b := bhp{unorderList} heap.Init(&b) for b.Len() > 0 { fmt.Println(heap.Pop(&b)) } }
1.5. 二叉树 1 2 3 4 5 type TreeNode struct { value int left *TreeNode right *TreeNode }
2. 算法 2.1. 全排列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func permutation (str []byte , index int , f func (str []byte ) ) { if len (str) == index { f(str) return } permutation(str, index+1 , f) for i := index + 1 ; i < len (str); i++ { str[i], str[index] = str[index], str[i] permutation(str, index+1 , f) str[i], str[index] = str[index], str[i] } } func main () { permutation([]byte ("abcd" ), 0 , func (str []byte ) { fmt.Println(string (str)) }) }
2.2. bfs 广度优先算法 1) 二叉树的广度优先遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type TreeNode struct { value int left *TreeNode right *TreeNode } func bfs (root *TreeNode) { var queue list.List queue.PushBack(&root) for queue.Len() > 0 { node := queue.Front().Value.(*TreeNode) queue.Remove(queue.Front()) if node.left != nil { queue.PushBack(node.left) } if node.right != nil { queue.PushBack(node.right) } fmt.Println(node.value) } }
2) 方格中找两点最短路径 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 type pointT struct { x int y int } var ( kRoundPoints = [][]int { {0 , -1 }, {0 , 1 }, {-1 , 0 }, {1 , 0 }, } ) func bfsFloor (src pointT, dst pointT, grid []string ) int { seen := make ([][]bool , len (grid)) for i := range seen { seen[i] = make ([]bool , len (grid[0 ])) } seen[src.y][src.x] = true curDepth := 0 var queue list.List queue.PushBack(src) queue.PushBack(nil ) for queue.Len() > 1 { tmp := queue.Front().Value queue.Remove(queue.Front()) if tmp == nil { curDepth++ queue.PushBack(nil ) continue } tx, ty := tmp.(pointT).x, tmp.(pointT).y if tx == dst.x && ty == dst.y { return curDepth } for i := range kRoundPoints { px, py := tx+kRoundPoints[i][0 ], ty+kRoundPoints[i][1 ] if py < 0 || py >= len (grid) || px < 0 || px >= len (grid[0 ]) || seen[py][px] || grid[py][px] == '#' { continue } seen[py][px] = true queue.PushBack(pointT{px, py}) } } return -1 } type pointST struct { x int y int step int } func bfs (src pointST, dst pointST, grid []string ) int { seen := make ([][]bool , len (grid)) for i := range seen { seen[i] = make ([]bool , len (grid[0 ])) } seen[src.y][src.x] = true var queue list.List queue.PushBack(src) for queue.Len() > 0 { tmp := queue.Front().Value.(pointST) queue.Remove(queue.Front()) if tmp.x == dst.x && tmp.y == dst.y { return tmp.step } for i := range kRoundPoints { px, py := tmp.x+kRoundPoints[i][0 ], tmp.y+kRoundPoints[i][1 ] if py < 0 || py >= len (grid) || px < 0 || px >= len (grid[0 ]) || seen[py][px] || grid[py][px] == '#' { continue } seen[py][px] = true queue.PushBack(pointST{px, py, tmp.step+1 }) } } return -1 } func main () { grid := []string {"@#..*" , "...#." , "#...." } fmt.Println(bfs(pointST{0 , 0 , 0 }, pointST{4 , 0 , 0 }, grid)) fmt.Println(bfsFloor(pointT{0 , 0 }, pointT{4 , 0 }, grid)) }
踩坑记 1. 编译报错 1.1. method has pointer receiverd 由于定义的接口多个方法的接受不一致导致 部分方法实现使用了指针,部分使用了原始类型 golang编译器发现*T类型实现了接口,调用的时候传入的又是T就给报错了 2. 协程相关 2.1. 循环起协程 迭代器会被更改,最终留下的是最后一个 应该当参数传入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func main () { var wg sync.WaitGroup a := []int {1 , 2 , 3 , 4 , 5 } for i := range a { wg.Add(2 ) go func () { defer wg.Done() fmt.Println("wrong" , i) }() go func (i int ) { defer wg.Done() fmt.Println("right" , i) }(i) } wg.Wait() }