目录
golang结构体
结构体的匿名字段
匿名字段 是指结构体中没有字段名的字段
结构体的嵌套
正常的结构体嵌套
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 type Address struct { City string Phone string } type Person struct { Name string Age int Address Address } func main () { user1 := Person{ Name: "Tom" , Age: 20 , Address: Address{ City: "Beijing" , Phone: "1234567890" , }, } user1.Address.City = "Shanghai" fmt.Println(user1.Address.City) }
结构体匿名字段嵌套
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 type Address struct { City string Phone string } type Person struct { Name string Age int Address } func main () { user1 := Person{ Name: "Tom" , Age: 20 , Address: Address{ City: "Beijing" , Phone: "1234567890" , }, } user1.City = "Shenzhen" fmt.Println(user1.Address.City) }
注意:
如果外层结构体与嵌套结构体有同名字段,使用user1.City访问的时候,会优先访问外层字段
如果两个嵌套匿名结构体有同名字段,而外层没有同名字段,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Address struct { City string Phone string } type Email stuct { Account string City string } type Person struct { Name string Age int Address Email } user1 := Person{}
定义方式如上,如果使用user1.City访问,会访问哪个字段呢,这样就会报错,必须使用user1.Address.City访问或者使用user1.Email.City访问,否则会报错
结构体的继承
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 package mainimport "fmt" type Animal struct { Name string } func (a Animal) run() { fmt.Println(a.Name, " is running!" ) } type Dog struct { Animal Age int } func (d Dog) wang() { fmt.Println(d.Name, " is wang!" ) } func main () { d := Dog{ Animal: Animal{ Name: "小狗" , }, Age: 4 , } d.run() d.wang() }
golang接口
接口的定义方式
1 2 3 4 type Animal interface { speak() eat() }
实现接口的方式
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 type Dog struct { name string } func (d Dog) speak() { fmt.Println(d.name, " says woof!" ) } func (d Dog) eat() { fmt.Println(d.name, " is eating meat!" ) } type Cat struct { name string } func (c Cat) speak() { fmt.Println(c.name, " says meow!" ) } func (c Cat) eat() { fmt.Println(c.name, " is eating fish!" ) } func main () { var animal Animal var dog Dog var cat Cat animal = dog animal.speak() animal.eat() cat.speak() cat.eat() }
使用接口的注意事项
1. 接口的定义中,**方法的签名必须一致,参数列表也必须一致。**
2. 接口的实现中,**必须实现接口中定义的所有方法。**
定义时值接受和指针接受的区别
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 package mainimport ( "fmt" ) type Usber interface { start() stop() } type Phone struct { Name string } func (p Phone) start() { fmt.Println(p.Name, "is starting" ) } func (p Phone) stop() { fmt.Println(p.Name, "is stopping" ) } func main () { var u1 Usber = Phone{Name: "xiaomi" } u1.start() u1.stop() var u Usber = &Phone{Name: "iPhone" } u.start() u.stop() }
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 package main import ( "fmt" ) type Usber interface { start() stop() } type Phone struct { Name string } func (p *Phone) start() { fmt.Println(p.Name, "is starting" ) } func (p *Phone) stop() { fmt.Println(p.Name, "is stopping" ) } func main () { var u Usber = &Phone{Name: "iPhone" }u.start() u.stop() }
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 package mainimport "fmt" type Animaler1 interface { SetName(string ) } type Animaler2 interface { GetName() string } type Dog struct { name string } func (d *Dog) SetName(name string ) { d.name = name } func (d Dog) GetName() string { return d.name } func main () { d := &Dog{"小黑" } var a1 Animaler1 = d var a2 Animaler2 = d fmt.Println(a2.GetName()) a1.SetName("小白" ) fmt.Println(a2.GetName()) }
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 package mainimport "fmt" type Animaler1 interface { SetName(string ) } type Animaler2 interface { GetName() string } type Animaler interface { Animaler1 Animaler2 } type Dog struct { name string } func (d *Dog) SetName(name string ) { d.name = name } func (d Dog) GetName() string { return d.name } func main () { d := &Dog{"小黑" } var a Animaler = d fmt.Println(a.GetName()) a.SetName("小白" ) fmt.Println(a.GetName()) }
空接口
没有定义任何方法的接口就叫做空接口,空接口表示没有任何约束,因此任何类型变量都可以实现空接口
1. 灵活性:由于空接口可以接受任何类型的值,它为编写更灵活的函数和数据结构提供了便利。例如,你可以编写一个接收空接口作为参数的函数,这个函数可以处理不同类型的输入。
1 2 3 func PrintValue (v interface {}) {fmt.Println(v) }
2. map的val为空接口,表示可以存储任意类型的值
1 2 3 4 5 var m map [string ]interface {}m = make (map [string ]interface {}) m["key" ] = 123 m["key" ] = "hello" fmt.Println(m["key" ])
3. 切片实现空接口
1 2 var slice = []interface {}{1 , "hello" , true }fmt.Println(slice)
4. 类型断言 :在使用空接口时,通常需要通过类型断言来获取实际值的具体类型。这使得在需要时可以安全地访问原始值。
1 2 3 4 5 6 var v interface {} = "Hello" str, ok := v.(string ); if ok { fmt.Println(str) }
5. 实现多态:通过使用空接口,可以实现某种程度的多态,使得不同类型的对象可以通过同一个接口进行操作。这在设计API时尤为有用。
空接口的使用细节,以及类型断言的一种使用方式
1. 空接口给切片或者map作为值类型的时候在使用的时候,不能详细访问里面的细节(详细下标,或者结构体里的值),例如:
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 package main import "fmt" type Address struct { City string Phone string } func main () { var mp map [string ]interface {} = make (map [string ]interface {}) mp["name" ] = "Alice" mp["age" ] = 25 mp["address" ] = Address{"New York" , "123-456-7890" } mp["arrays" ] = []int {1 , 2 , 3 } v, ok := mp["address" ].(Address) if ok { fmt.Println(v.City) } k, ok := mp["arrays" ].([]int ) if ok { fmt.Println(k[0 ]) } fmt.Println(mp["arrays" ].([]int )[1 ]) }
golang协程
进程,线程,协程之间的区别
获取及设置本机CPU核数
1 2 3 4 5 nums := runtime.NumCPU() fmt.Println("CPU核数:" , nums) runtime.GOMAXPROCS(nums - 1 ) fmt.Println("CPU核数:" , runtime.GOMAXPROCS(0 ))
以上两种方式 一个属获取本机cpu核数,一个是(runtime.GOMAXPROCS 函数 )设置项目中的最大协程数且返回调用之前的最大线程数(即之前的 GOMAXPROCS 值)。当传入0的时候,表示只获取值而不修改
协程的创建方式
使用go关键字创建协程
使用go 关键字即可创建, go后面跟着协程要执行的任务函数
协程的同步方式
使用sync.WaitGroup来等待协程执行完毕
golang管道
管道底层的数据结构
首先管道支持多个协程访问 ,所以底层肯定有锁 ,其次管道可能会有缓冲区,所以需要指向缓冲区的指针 以及最大长度 还有已经存储了多少数据 以及每个元素占用内存大小和其类型 ,
由于管道会阻塞所以有两个队列,发送队列和接受队列 ,这两个队列来存储被阻塞的协程,由于是基于缓冲区的,所以会有一个写下标和读下标 ,写下标表示管道中可以写入数据的位置,
读下标表示管道中可以读取数据的位置,最后还有一个标志是否关闭的状态位 ,其内容大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 stuct chan { lock mutex qcount uint dataqsiz uint buf unsafe.Pointer elemsize uint16 closed uint32 sendx uint recvx uint recvq waitq sendq waitq elemtype *_type }
select的底层原理
会被编译器翻译成执行selectgo函数,其函数由6个参数和两个返回值
参数:首先由一个指向一个数组,里面装的是select中所有的case分支,第二个参数是指向一个uint16类型的数组,其大小等于case的两倍。实际会被分为两个数组,一个数组用来乱序轮询(保证公平性),一个用来对所有加锁操作进行排序(避免死锁),第四第五个参数分别表示send和recv操作的分支分别有多少个
block参数则是表示多路select是否要阻塞等待(对应是否由default分支决定)
返回值:第一个返回值表示哪个分支被执行(对应数组下标,如果阻塞为-1),第二个表示是实际收到了一个值还是得到了0值
大致如下:
select的执行过程
首先对所有管道进行顺序加锁,然后按照乱序的轮询顺序检查所有channel的等待队列和缓冲区,接下来就是channel相关操作的判断看是否需要阻塞,如果阻塞了就要挂起等待,当有数据时就按序解锁并唤醒执行,执行完后按序加锁,阻塞协程离开队列最后按序解锁
go的反射机制
反射机制的定义
在计算机学中,反射式编程(英语:reflective programming)或反射(英语:reflection),是指计算机程序(runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
反射的重要性
反射在许多方面都非常有用,比如:
动态编程 : 通过反射,你可以动态地创建对象,调用方法,甚至构建全新的类型。
框架与库开发 : 很多流行的Go框架,如Gin、Beego等,都在内部使用反射来实现灵活和高度可定制的功能。
元编程 : 你可以写出可以自我分析和自我修改的代码,这在配置管理、依赖注入等场景中尤为有用。
反射的分类
反射在Go中主要有两个方向:
类型反射(Type Reflection): 主要关注于程序运行时获取变量的类型信息。
值反射(Value Reflection): 主要关注于程序运行时获取或设置变量的值
类型反射:
通过reflect库中的Typeof函数实现,Kind就是获取其底层的类型 ,比如你定义一个Person结构体,Kind就是struct而Name就是Person
1 2 3 4 5 6 7 8 9 10 11 func inspectType (x interface {}) { t := reflect.TypeOf(x) fmt.Println("Type Name:" , t.Name()) fmt.Println("Type Kind:" , t.Kind()) } func main () { inspectType(42 ) inspectType("hello" ) }
值反射
1 2 3 4 5 6 7 8 9 10 11 func inspectValue (x interface {}) { v := reflect.ValueOf(x) fmt.Println("Value:" , v) fmt.Println("Is Zero:" , v.IsZero()) } func main () { inspectValue(42 ) inspectValue("" ) }
反射的应用
与空接口配合使用,得到真实数据
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 package mainimport ( "fmt" "reflect" ) func reflectValue (value interface {}) { v := reflect.ValueOf(value) switch v.Kind() { case reflect.Int: fmt.Println("int value:" , v.Int()+10 ) case reflect.String: fmt.Println("string value:" , v.String()+" world" ) case reflect.Bool: fmt.Println("bool value:" , !v.Bool()) case reflect.Float32: fmt.Println("float32 value:" , v.Float()+1.0 ) default : fmt.Println("unknown type" ) } x, ok := value.(int ) if ok { fmt.Println("int value:" , x+10 ) } y, ok := value.(string ) if ok { fmt.Println("string value:" , y+" world" ) } z, ok := value.(bool ) if ok { fmt.Println("bool value:" , !z) } h, ok := value.(float32 ) if ok { fmt.Println("float32 value:" , h+1.0 ) } } func main () { var a int = 10 var b string = "hello" var c bool = true var d float32 = 3.14 reflectValue(a) reflectValue(b) reflectValue(c) reflectValue(d) }
通过反射设置变量的值
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 package mainimport ( "fmt" "reflect" ) func SetVal (val interface {}) { v := reflect.ValueOf(val).Elem() fmt.Println(v.Kind()) if v.Kind() == reflect.Int { v.SetInt(100 ) } else if v.Kind() == reflect.String { v.SetString("new string" ) } } func main () { var a = 10 b := "string" SetVal(&a) SetVal(&b) fmt.Println(a) fmt.Println(b) }
反射获取结构体的值与类型
获取属性
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 ( "fmt" "reflect" ) type Person struct { Name string `json:"name"` Age int `json:"age"` Address string `json:"address"` } func GetStruct (p interface {}) { t := reflect.TypeOf(p) field0 := t.Field(0 ) fmt.Println("第一个字段名" , field0.Name) fmt.Println("第一个字段类型" , field0.Type) fmt.Println("第一个字段标签" , field0.Tag.Get("json" )) } func main () { person1 := Person{"Tom" , 20 , "China" } GetStruct(person1) }
使用FiledByName(name)来获取指定字段
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" "reflect" ) type Person struct { Name string `json:"name"` Age int `json:"age"` Address string `json:"address"` } func GetStruct (p interface {}) { t := reflect.TypeOf(p) field0, ok := t.FieldByName("Age" ) if ok { fmt.Println("第一个字段名" , field0.Name) fmt.Println("第一个字段类型" , field0.Type) fmt.Println("第一个字段标签" , field0.Tag.Get("json" )) } } func main () { person1 := Person{"Tom" , 20 , "China" } GetStruct(person1) }
通过NumField()来获取结构体的字段数量,并且遍历所有字段
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" "reflect" ) type Person struct { Name string `json:"name"` Age int `json:"age"` Address string `json:"address"` } func GetStruct (p interface {}) { t := reflect.TypeOf(p) for i := 0 ; i < t.NumField(); i++ { fmt.Printf("第%d个字段的名称为:%s\n" , i, t.Field(i).Name) fmt.Printf("第%d个字段的类型为:%s\n" , i, t.Field(i).Type) } } func main () { person1 := Person{"Tom" , 20 , "China" } GetStruct(person1) }
获取值也有上述三种方法,不过反射的时候改为Valueof,例如
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" "reflect" ) type Person struct { Name string `json:"name"` Age int `json:"age"` Address string `json:"address"` } func GetStruct (p interface {}) { t := reflect.ValueOf(p) for i := 0 ; i < t.NumField(); i++ { fmt.Printf("第%d个字段的值为:%v\n" , i, t.Field(i)) } } func main () { person1 := Person{"Tom" , 20 , "China" } GetStruct(person1) }
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 package mainimport ( "fmt" "reflect" ) type Person struct { Name string `json:"name"` Age int `json:"age"` Address string `json:"address"` } func (p Person) GetPerson() string { var str = fmt.Sprintf("%+v\n" , p) return str } func (p *Person) SetPerson(name string , age int , address string ) { p.Name = name p.Age = age p.Address = address } func (p Person) Print() { fmt.Println(p.GetPerson()) } func SetStructField (p interface {}, key string , str string ) { v := reflect.ValueOf(p) if v.Kind() != reflect.Ptr { fmt.Println("not a pointer" ) return } if v.Elem().Kind() != reflect.Struct { fmt.Println("not a struct pointer" ) return } v.Elem().FieldByName(key).SetString(str) } func main () { person1 := Person{"Tom" , 20 , "China" } fmt.Println(person1.GetPerson()) SetStructField(&person1, "Name" , "Jerry" ) fmt.Println(person1.GetPerson()) }
获取方法
使用Method(i)来获取方法,其中方法的顺序是通过Ascii码确定的+NumMethod()来获取方法数量
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 package mainimport ( "fmt" "reflect" ) type Person struct { Name string `json:"name"` Age int `json:"age"` Address string `json:"address"` } func (p Person) GetPerson() string { var str = fmt.Sprintf("%+v\n" , p) return str } func (p *Person) SetPerson(name string , age int , address string ) { p.Name = name p.Age = age p.Address = address } func (p Person) Print() { fmt.Println(p.GetPerson()) } func GetStructMethod (p interface {}) { t := reflect.TypeOf(p) for i := 0 ; i < t.NumMethod(); i++ { fmt.Printf("第%d个方法的名称为:%v\n" , i, t.Method(i).Name) fmt.Printf("第%d个方法的类型为:%v\n" , i, t.Method(i).Type) } } func main () { person1 := Person{"Tom" , 20 , "China" } GetStructMethod(person1) }
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" "reflect" ) type Person struct { Name string `json:"name"` Age int `json:"age"` Address string `json:"address"` } func (p Person) GetPerson() string { var str = fmt.Sprintf("%+v\n" , p) return str } func (p *Person) SetPerson(name string , age int , address string ) { p.Name = name p.Age = age p.Address = address } func (p Person) Print() { fmt.Println(p.GetPerson()) } func GetStructMethod (p interface {}) { t := reflect.TypeOf(p) method1, ok := t.MethodByName("Print" ) if ok { fmt.Println(method1.Name) fmt.Println(method1.Type) } } func main () { person1 := Person{"Tom" , 20 , "China" } GetStructMethod(person1) }
反射调用方法
首先必须通过反射获取值也就是使用felect.ValueOf(),然后通过反射调用方法,调用方法的语法如下:修改方法必须传入引用
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 mainimport ( "fmt" "reflect" ) type Person struct { Name string `json:"name"` Age int `json:"age"` Address string `json:"address"` } func (p Person) GetPerson() string { var str = fmt.Sprintf("%+v\n" , p) return str } func (p *Person) SetPerson(name string , age int , address string ) { p.Name = name p.Age = age p.Address = address } func (p Person) Print() { fmt.Println(p.GetPerson()) } func GetStructMethod (p interface {}) { v := reflect.ValueOf(p) var args []reflect.Value args = append (args, reflect.ValueOf("小黑" ), reflect.ValueOf(18 ), reflect.ValueOf("Earth" )) v.MethodByName("SetPerson" ).Call(args) v.MethodByName("Print" ).Call(nil ) } func main () { person1 := Person{"Tom" , 20 , "China" } GetStructMethod(&person1) }
go的文件操作
使用os打开并使用其返回的file读取文件
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 package mainimport ( "fmt" "io" "os" ) func main () { file, err := os.Open("./main.go" ) if err != nil { fmt.Println(err) return } defer file.Close() var endSlice []byte tmpSlice := make ([]byte , 128 ) for { n, err := file.Read(tmpSlice) if err == io.EOF { fmt.Println("文件读取完毕" ) break } if err != nil { fmt.Println("文件读取失败" ) return } endSlice = append (endSlice, tmpSlice[:n]...) } fmt.Println(string (endSlice)) }
使用bufio读取数据
用此方法时候注意在读取到EOF的时候也要累加数据,否则会丢失数据
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 package mainimport ( "bufio" "fmt" "io" "os" ) func main () { file, err := os.Open("./main.go" ) if err != nil { fmt.Println(err) return } defer file.Close() reader := bufio.NewReader(file) var ans string for { line, err := reader.ReadString('\n' ) if err == io.EOF { ans += line break } if err != nil { fmt.Println(err) return } ans += line } fmt.Println(ans) }
以上两种都是流的读取方法
下述是一次读取整个文件的方法
使用ioutil读取文件
此方法在go1.16以上版本被弃用,可以使用os.ReadFile()代替,因为这个实现上就是用了os.ReadFile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" "os" ) func main () { slice, err := os.ReadFile("./main.go" ) if err != nil { fmt.Println(err) return } fmt.Println(string (slice)) }
os.openfile()和file.Write()写入文件
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 ( "fmt" "os" "strconv" ) func main () { file, err := os.OpenFile("./test.txt" , os.O_CREATE|os.O_RDWR, 0666 ) if err != nil { fmt.Println(err) return } defer file.Close() for i := 0 ; i < 10 ; i++ { file.WriteString("hello world" + strconv.Itoa(i) + "\r\n" ) } str := "omg no hello world \r\n" file.Write([]byte (str)) }
使用bufio.writer写入文件
总体流程是这样的:
先通过os.OpenFile打开文件,并通过bufio.NewWriter创建一个bufio.Writer
然后通过WriteString或Write方法写入数据(此时会先写入缓存区 )
最后通过Flush方法将缓存区的数据写入文件
最后关闭文件
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 ( "bufio" "fmt" "os" ) func main () { file, err := os.OpenFile("./test.txt" , os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666 ) if err != nil { fmt.Println(err) return } defer file.Close() writer := bufio.NewWriter(file) writer.WriteString("hello world zxsssn\r\n" ) writer.Flush() }
使用os.WriteFile()写入文件
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "os" ) func main () { err := os.WriteFile("./test.txt" , []byte ("hello world" ), 0666 ) if err != nil { return } }
go排序方法
排序整数,浮点数和字符串切片
对于 []int, []float, []string 这种元素类型是基础类型的切片使用 sort 包提供的下面几个函数进行排序。
sort.Ints(slice)
sort.Float64s(slice)
sort.Strings(slice)
使用自定义比较器排序
使用 sort.Slice 函数排序,它使用一个用户提供的函数来对序列进行排序,函数类型为 func(i, j int) bool,其中参数 i, j 是序列中的索引。
sort.SliceStable 在排序切片时会保留相等元素的原始顺序。
上面两个函数让我们可以排序结构体切片 (order by struct field 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 35 36 37 38 package mainimport ( "fmt" "sort" ) type Person struct { Key string Value int } func main () { people := []Person{ {"Alice" , 25 }, {"Cob" , 30 }, {"Vharlie" , 20 }, {"Favid" , 35 }, {"Eve" , 28 }, } fmt.Println(people) sort.Slice(people, func (i, j int ) bool { return people[i].Key < people[j].Key }) fmt.Println(people) sort.SliceStable(people, func (i, j int ) bool { return people[i].Key > people[j].Key }) fmt.Println(people) }
排序任意数据结构
使用 sort.Sort 或者 sort.Stable 函数。
他们可以排序实现了 sort.Interface 接口的任意类型
也就是说每个自定义的切片中都有一些内置的接口,比如sort.Interface,sort.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 29 30 31 32 33 34 35 36 package mainimport ( "fmt" "sort" ) type Person struct { Key string Value int } type ByValue []Personfunc (a ByValue) Len() int { return len (a) }func (a ByValue) Less(i, j int ) bool { return a[i].Value < a[j].Value }func (a ByValue) Swap(i, j int ) { a[i], a[j] = a[j], a[i] }func main () { people := []Person{ {"Alice" , 25 }, {"Cob" , 30 }, {"Vharlie" , 20 }, {"Favid" , 35 }, {"Eve" , 28 }, } fmt.Println(people) sort.Sort(ByValue(people)) fmt.Println(people) }
1 2 3 4 5 6 7 8 type customSort struct { p []*Person less func (x, y *Person) bool } func (x customSort) Len() int {len (x.p)}func (x customSort) Less(i, j int ) bool { return x.less(x.p[i], x.p[j]) }func (x customSort) Swap(i, j int ) { x.p[i], x.p[j] = x.p[j], x.p[i] }
让我们定义一个根据多字段排序的函数,它主要的排序键是 Age,Age 相同了再按 Name 进行倒序排序。下面是该排序的调用,其中这个排序使用了匿名排序函数:
1 2 3 4 5 6 7 8 9 sort.Sort(customSort{persons, func (x, y *Person) bool { if x.Age != y.Age { return x.Age < y.Age } if x.Name != y.Name { return x.Name > y.Name } return false }})
排序具体的算法和复杂度
Go 的 sort 包中所有的排序算法在最坏的情况下会做 n log n 次 比较,n 是被排序序列的长度,所以排序的时间复杂度是 O(n log n*)。其大多数的函数都是用改良后的快速排序算法实现的。
go序列化相关知识
Json序列化
GO提供了 Marshal 方法:Go Struct转换为JSON对象,函数签名:
1 func Marshal (v interface {}) ([]byte , error )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Person struct { Name string Gender string Age uint32 } p := Person{"a" , "male" , 23 } fmt.Println(p) fmt.Printf("%v\n" , p) p1 := Person{Name: "wsq" , Gender: "male" } fmt.Println(p1) b, _ := json.Marshal(p) fmt.Println(string (b)) }
只支持struct中导出的field才能被序列化,即首字母大写的field
GO中不是所有类型都支持序列化,其中key只支持string
无法对channel,complex,function序列化
数据中如存在循环引用,不支持序列化,因为会递归。
pointer序列化后是其指向的值或者是nil
Struct Tag
指定 JSON filed name
序列化后的json串中的name一般为小写,我们通过struct tag实现
指定field为empty
使用omitempty告诉Marshal函数,如field对应类型为zero-value,那么序列化的json对象中不包含此field
跳过field
仅使用"-"表示跳过指定的field,保护某些字段不被序列化
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 type Person struct { Name string `json:"name"` Gender string `json:"gender"` Age uint32 `json:"age,omitempty"` Passwd string `json:"-"` } p := Person{"a" , "male" , 23 , "mimi" } fmt.Println(p) fmt.Printf("%v\n" , p) p1 := Person{Name: "wsq" , Gender: "male" } fmt.Println(p1) b, _ := json.Marshal(p) fmt.Println(string (b)) var pp Person err := json.Unmarshal(b, &pp) if err != nil { errors.New("unmarshal error" ) } fmt.Printf("%T, %v\n" , pp, pp) c, _ := json.Marshal(p) fmt.Println(string (c)) d, _ := json.Marshal(p1) fmt.Println(string (d)) importPerson := Person{Name: "wsq" , Passwd: "password" } importPersonMar, _ := json.Marshal(importPerson) fmt.Println(string (importPersonMar))
Json反序列化
GO提供了 Unmarshal 方法:JSON对象转换为Go Struct,函数签名:
1 func Unmarshal (data []byte , v interface {}) error
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 Person struct { Name string `json:"name"` Gender string `json:"gender"` Age uint32 `json:"age,omitempty"` Passwd string `json:"-"` } p := Person{"a" , "male" , 23 , "mimi" } fmt.Println(p) fmt.Printf("%v\n" , p) p1 := Person{Name: "wsq" , Gender: "male" } fmt.Println(p1) b, _ := json.Marshal(p) fmt.Println(string (b)) var pp Person err := json.Unmarshal(b, &pp) if err != nil { errors.New("unmarshal error" ) } fmt.Printf("%T, %v\n" , pp, pp)
使用Encoder和Decoder进行序列化和反序列化
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 type Person struct { Name string `json:"name"` Age int `json:"age"` } func main () { var err error person1 := Person{"张三" , 30 } bytes1 := new (bytes.Buffer) _ = json.NewEncoder(bytes1).Encode(person1) if err == nil { fmt.Print("json.NewEncoder 编码结果: " , string (bytes1.Bytes())) } str2 := bytes1.String() var person2 Person err = json.NewDecoder(strings.NewReader(str2)).Decode(&person2) if err == nil { fmt.Println("json.NewDecoder 解码结果: " , person2.Name, person2.Age) } }
gob序列化
Gob 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式;可以在 encoding 包中找到。 这种格式的数据简称为 Gob (即 Go binary 的缩写)。 类似于 Python 的 “pickle” 和 Java 的 “Serialization”。 Gob 通常用于远程方法调用(RPCs,参见 15.9 的 rpc 包)参数和结果的传输,以及应用程序和机器之间的数据传输。
Gob序列化流程
其调用流程和json类似
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 package mainimport ( "bytes" "fmt" "encoding/gob" "log" ) type P struct { X, Y, Z int Name string } type Q struct { X, Y *int32 Name string } func main () { var network bytes.Buffer enc := gob.NewEncoder(&network) dec := gob.NewDecoder(&network) err := enc.Encode(P{3 , 4 , 5 , "Pythagoras" }) if err != nil { log.Fatal("encode error:" , err) } var q Q err = dec.Decode(&q) if err != nil { log.Fatal("decode error:" , err) } fmt.Printf("%q: {%d,%d}\n" , q.Name, *q.X, *q.Y) }
该例子是以字节缓冲模拟网络传输的简单例子
go的内置定时器的使用
在go的time包中一共提供了三种定时器的使用方式:
Timer:定时器,只执行一次
After():定时器,只执行一次,返回一个通道,可以接收定时器的执行结果
Tick:定时器,周期性执行,返回一个通道,可以接收定时器的执行结果
Ticker定时器
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 main import ( "fmt" "time" ) func main () { ticker := time.NewTicker(time.Second * 1 ) fmt.Println("start" ) ch := make (chan int ) go func () { var x int for x < 10 { select { case <-ticker.C: x++ fmt.Printf("%d\n" , x) } } ticker.Stop() ch <- 0 }() <-ch }
每次超时后,Ticker会向内部的管道发送当前时间,也就是说我们可以通过内部管道是否有新消息到来来判断是否超时。
Timer定时器
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" "time" ) func main () { Timer := time.NewTimer(time.Second * 1 ) ch := make (chan int ) go func () { x := 0 for { select { case <-Timer.C: fmt.Println("Timer expired" ) x++ if x <= 10 { fmt.Println(x) Timer.Reset(time.Second) } else { ch <- 0 } } } }() <-ch }
After()
1 2 3 func After (d Duration) <-chan Time { return NewTimer(d).C }
从定义来看after函数只是timer的语法糖
使用方式如下:
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 ( "fmt" "time" ) func main () { ch := make (chan int ) go func () { tt := time.NewTimer(time.Second * 2 ) <-tt.C fmt.Println("over." ) <-time.After(time.Second * 4 ) fmt.Println("再等待4秒退出。tt 没有终止,打印出 over 后会看见在继续执行..." ) tt.Stop() t := <-time.After(time.Second * 2 ) fmt.Println("tt.Stop()后, tt 仍继续执行,只是关闭了 tt.C 通道。" , t) ch <- 1 }() <-ch }
1 time.Now().After(t time.Time) bool
做项目遇到的一些问题
Map通过索引返回的是副本,改变副本结构体不会改变map中的结构体原数据
将一个变量声明为map[int]Task 类型,然后我想要通过索引修改Task结构体的内容(map[task.id ].status = “done”)会出错 ,提示"cannot assign to struct field value in map"
原因:Go 的行为: 在 Go 中,map[] 返回的是结构体的副本,因此无法直接修改 map 中的结构体字段。需要取出副本、修改后再放回,这与C++不一样
不同进程运行相同包下的不同源代码文件的一些现象
首先进程间的数据不共享 ,而且你在两个源文件定义的东西,都会在进程中被声明与初始化,但是你在一个进程中改变某值并不会影响另外一个值
例如: 你在A.go中定义了numReduce初始化为10,然后你在A.go中修改为20,A.go运行在一个进程中,B.go在另外一个进程运行,获取A的值只能获得10,因为两个进程中A.go的numReduce的值是不共享的。
同理使用单例模式也一样,在A进程中初始化了单例,在B进程再次获得单例的时候,还是会初始化一次,而且使用的是B进程内的变量
我在写项目的时候并没有第一时间发现这个问题,导致找bug花费了不少时间
select 配合通道使用的时候的一些逻辑
select 配合通道使用的时候,如果没有任何case可以执行,则会阻塞,直到有case可以执行
select 配合通道使用的时候,如果有多个case都可以执行,则会随机选择一个执行
通道先有数据的情况下,会执行现有数据的case,然后退出select