面向对象
- go没有class关键字,oop,但是我们可以把go当做面向对象的方式来编程
- go没有类,可以把struct作为类看待
- 类的方法是啥: 给struct绑定的方法
继承
- 通过结构体的匿名嵌套,继承对应的字段和方法,举例 ```go package main
import ( "log" )
type Person struct { Name string Age int }
type Student struct { Person StudentId int }
// 给Person结构体绑定一个SayHello func (p Person) SayHello() { log.Printf("[Person.SayHello][name:%v]", p.Name) }
func main() { p1 := Person{ Name: "小乙", Age: 123, }
s1 := Student{
Person: p1,
StudentId: 99,
}
s1.SayHello()
}
## 结构体单例绑定
```go
package main
import (
"log"
)
type Person struct {
Name string
Age int
}
type Student struct {
Person
StudentId int
}
// 给Person结构体绑定一个SayHello
func (p Person) SayHello() {
log.Printf("[Person.SayHello][name:%v]", p.Name)
}
func (p *Person) ChangeAge1() {
p.Age += 10
log.Printf("[单实例绑定方法][Person.ChangeAge1][p.Age:%v]", p.Age)
}
func (p Person) ChangeAge2() {
p.Age += 10
log.Printf("[非指针型绑定][Person.ChangeAge2][p.Age:%v]", p.Age)
}
func main() {
p1 := Person{
Name: "小乙",
Age: 123,
}
s1 := Student{
Person: p1,
StudentId: 99,
}
s1.SayHello()
log.Println(s1.Age)
s1.ChangeAge1()
log.Println(s1.Age)
log.Println(s1.Age)
s1.ChangeAge2()
log.Println(s1.Age)
}
多态 :通过接口做多态
go接口
- interface{} 定义方法的集合
- 多态体现在,各个结构体对象要实现 接口中定义的所有方法
- 统一的函数调用入口 ,传入的接口
- 各个结构体对象中 绑定的方法只能多不能少于 接口定义的
- 方法的签名要一致:参数类型、参数个数,方法名称,函数返回值要一致
多态的灵魂 是什么
是有一个承载的容器,先把所有实现了接口的对象添加进来。遍历容器调用对应的方法
举例 ```go package main
import "log"
// 体现多态 // 告警通知的函数,根据不同的对象进行通知 // type notifer interface { // 通知方法 notify() }
type user struct { name string email string }
func (u *user) notify() { log.Printf("[普通用户的通知][notify to user :%s]", u.name) }
type admin struct { name string age int }
func (u *admin) notify() { log.Printf("[管理员的通知][notify to user :%s]", u.name) }
// 多态的统一调用入口 func sendNotify(n notifer) { n.notify() }
func main() {
u1 := user{
name: "小乙",
email: "xy@qq.com",
}
a1 := admin{
name: "燕青",
age: 18,
}
// 直接调用结构体绑定的方法
log.Println("直接调用结构体绑定的方法")
u1.notify()
a1.notify()
// 体现多态
log.Println("体现多态")
sendNotify(&u1)
sendNotify(&a1)
// 灵魂
log.Println("多态灵魂承载容器")
ns := make([]notifer, 0)
ns = append(ns, &a1)
ns = append(ns, &u1)
for _, n := range ns {
n.notify()
}
}
### 实际应用 prometheus 的alert和record

## 常用举例 ,多个数据源推送数据和查询数据
- 举例
```go
package main
import (
"fmt"
"log"
)
// 多个数据源推送数据和查询数据
// query 查询数据
// push 方法写入数据
type DataSource interface {
Push(data string)
Query(name string) string
}
type redis struct {
Name string
Addr string
}
func (r *redis) Push(data string) {
// 真实应该推入它消息队列
log.Printf("[Pushdata][ds.name:%s][data:%s]", r.Name, data)
}
func (r *redis) Query(name string) string {
log.Printf("[Query.data][ds.name:%s][name:%s]", r.Name, name)
return name +"-"+ r.Name
}
type kafka struct {
Name string
Addr string
}
func (k *kafka) Push(data string) {
// 真实应该推入它消息队列
log.Printf("[Pushdata][ds.name:%s][data:%s]", k.Name, data)
}
func (k *kafka) Query(name string) string {
log.Printf("[Query.data][ds.name:%s][name:%s]", k.Name, name)
return name +"-"+ k.Name
}
// 灵魂容器
var DataSourceManager = make(map[string]DataSource)
// 注册方法
func register(name string, ds DataSource) {
DataSourceManager[name] = ds
}
func main() {
r := redis{
Name: "redis-6.0",
Addr: "1.1",
}
k := kafka{
Name: "kafka-2.11",
Addr: "2.2",
}
// 将数据源注册到承载的容器中
register("redis", &r)
register("kafka", &k)
// 模拟推送数据
for i := 0; i < 10; i++ {
key := fmt.Sprintf("key_%d", i)
for _, ds := range DataSourceManager {
ds.Push(key)
}
}
// 查询数据
for i := 0; i < 10; i++ {
key := fmt.Sprintf("key_%d", i)
for _, ds := range DataSourceManager {
log.Println(ds.Query(key))
}
}
}
空接口
- 所有的类型都实现了空接口
类型断言 和类型判断
- 一个interface 需要类型转换的时候,语法 i.(T)
- v,ok:=i.(T) ,ok=true代表断言成功,ok=false v是这个类型的0值
- 举例 ```go package main
import "fmt"
func main() {
var s interface{} = false
//s1, ok := s.(string)
//fmt.Println(s1, ok)
//
//s2, ok := s.(int)
//fmt.Println(s2, ok )
switch s.(type) {
case string:
fmt.Println("是个string")
case int:
fmt.Println("是个int")
default:
fmt.Println("未知的type")
}
}
## hook机制
- logrus 的hook机制 github.com/sirupsen/logrus
# go语言错误
- error类型是一个接口类型,这是它的定义:
```go
type error interface {
Error() string
}
if err!=nil
- 函数调用时判断返回值
errors.New 创建error
package main
import (
"errors"
"log"
"strings"
)
func validate(name string) (ok bool, err error) {
if !strings.HasPrefix(name, "mysql") {
return false, errors.New("name must start with mysql")
}
return true, nil
}
func main() {
s1 := "mysql-abc"
s2 := "redis-abc"
_, err := validate(s1)
if err != nil {
log.Printf("[judge1][validate][err:%v]", err)
}
if ok, err := validate(s2); err != nil {
log.Printf("[judge2][validate][err:%v][ok:%v]", err, ok)
}
}
复杂的错误类型
- 以os包举例 提供了 LinkError、PathError、SyscallError
- 这些实现了 error 接口的错误类型,以 PathError 为例,顾名思义,它主要用于表示路径相关的错误信息,比如文件不存在,其底层类型结构信息如下:
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
- 样例 ```go package main
import ( "log" "os" )
func main() {
f1, err := os.Stat("test.txt")
if err != nil {
switch err.(type) {
case *os.PathError:
log.Printf("PathError")
case *os.LinkError:
log.Printf("LinkError")
case *os.SyscallError:
log.Printf("SyscallError")
}
} else {
log.Printf("f1:%v", f1)
}
}
## 自定义error
- 使用结构体额外的msg字段扩展
- 自定义自己的struct,添加用于存储我们需要额外信息的字段
- 缺点就是定义很多结构体
```go
package main
import (
"errors"
"fmt"
)
type MyError struct {
err error
msg string
}
func (e *MyError) Error() string {
return e.err.Error() + e.msg
}
func main() {
err := errors.New("原始错误")
myErr := MyError{
err: err,
msg: " 数据上传问题",
}
fmt.Println(myErr.Error())
}
golang 1.13中的Error Wrapping 错误嵌套
- 用来给错误添加额外信息的信息
- Golang并没有提供什么Wrap函数,而是扩展了fmt.Errorf函数,加了一个%w来生成一个可以Wrapping Error ```go package main
import ( "errors" "fmt" )
func main() {
e1 := errors.New("原始错误")
e2 := fmt.Errorf("数据删除错误 :%w", e1)
fmt.Println(e2)
}
# I/O操作也叫输入输出操作
- 其中I是指Input,O是指Output
- 用于读或者写数据的,有些语言中也叫流操作,是指数据通信的通道。
# Go 输入和输出操作是使用原语实现的
- 这些原语将数据模拟成可读的或可写的字节流。
- Go 的 io 包提供了 io.Reader 和 io.Writer 接口
- 分别用于数据的输入和输出

# io库属于底层接口定义库
- 其作用是是定义一些基本接口和一些基本常量
- 一般用这个库只是为了调用它的一些常量,比如io.EOF
# io库比较常用的接口有三个,分别是Reader,Writer和Close。
- io库中实现的接口可以以流的方式高效处理数据,而不用考虑数据是什么,数据来自哪里,以及数据要发送到哪里的问题
## Reader
- io.Reader 表示一个读取器,它将数据从某个资源读取到传输缓冲区。在缓冲区中,数据可以被流式传输和使用。

```go
type Reader interface {
Read(p []byte) (n int, err error)
}
- 对于要用作读取器的类型,它必须实现 io.Reader 接口的唯一一个方法 Read(p []byte)。
- 换句话说,只要实现了 Read(p []byte) ,那它就是一个读取器。
- Read() 方法有两个返回值,一个是读取到的字节数,一个是发生错误时的错误。
- 通过 string.NewReader(string) 创建一个字符串读取器,然后流式地按字节读取:
package main
import (
"io"
"log"
"os"
"strings"
)
func main() {
reader := strings.NewReader("xiaoyi 123dwd 123")
// 每次读取4个字节
p := make([]byte, 4)
for {
n, err := reader.Read(p)
if err != nil {
if err == io.EOF {
log.Printf("读完了:eof错误 :%d", n)
break
}
log.Printf("其他错误:%v", err)
os.Exit(2)
}
log.Printf("[读取到的字节数为:%d][内容:%v]", n, string(p[:n]))
log.Printf("[读取到的字节数为:%d][内容:%v]", n, string(p))
}
}
- 可以看到,最后一次返回的 n 值有可能小于缓冲区大小。
- io.EOF 来表示输入流已经读取到头
查看strings.Reader.Read方法 文件在 C:\Program Files\Go\src\strings\reader.go
func (r *Reader) Read(b []byte) (n int, err error) { if r.i >= int64(len(r.s)) { return 0, io.EOF } r.prevRune = -1 n = copy(b, r.s[r.i:]) r.i += int64(n) return }
自己实现一个 strings.Reader
- 举例 a-z A-Z
组合多个 Reader,目的是重用和屏蔽下层实现的复杂度
- 标准库已经实现了许多 Reader。
- 使用一个 Reader 作为另一个 Reader 的实现是一种常见的用法。
- 这样做可以让一个 Reader 重用另一个 Reader 的逻辑,下面展示通过更新 alphaReader 以接受 io.Reader 作为其来源。
package main
import (
"io"
"log"
"strings"
)
type alphaReader struct {
// 组合io.reader
reader io.Reader
}
func (a *alphaReader) Read(p []byte) (int, error) {
// 这里调用的是io.Reader
n, err := a.reader.Read(p)
if err != nil {
return n, err
}
buf := make([]byte, n)
for i := 0; i < n; i++ {
if char := guolv(p[i]); char != 0 {
buf[i] = char
}
}
copy(p, buf)
return n, nil
}
// 只保留字符串中的字母字符 a-z A-Z
func guolv(r byte) byte {
if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') {
return r
}
return 0
}
func main() {
originReader := strings.NewReader("xiaoYX @dakdwd 1213kwdwd!!")
reader := alphaReader{
reader: originReader,
}
p1 := make([]byte, 4)
for {
n1, err := reader.Read(p1)
if err == io.EOF {
break
}
log.Printf("[][内容:%v]", string(p1[:n1]))
}
}
os.File 结合
- 以下代码展示了 alphaReader 如何与 os.File 结合以过滤掉文件中的非字母字符: ```go package main
import ( "fmt" "io" "log" "os" )
var a byte = 0
type alphaReader struct { // 组合io.reader reader io.Reader }
func (a *alphaReader) Read(p []byte) (int, error) { // 这里调用的是io.Reader n, err := a.reader.Read(p) if err != nil { return n, err } // 此时的p [xi@a] [xiao] buf := make([]byte, n) // buf = [0 0 0 0] fmt.Println(buf) for i := 0; i < n; i++ { if char := guolv(p[i]); char != 0 { buf[i] = char } } fmt.Println(buf) // buf = [79 68 0 65] copy(p, buf) return n, nil }
// 只保留字符串中的字母字符 a-z A-Z func guolv(r byte) byte { if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') { return r } return 0 }
func main() { file,err:=os.Open("a.txt") if err!=nil{ return } //originReader := strings.NewReader("xiaoYX(@dakdwd[1213kwdwd!!") reader := alphaReader{ reader: file, } p1 := make([]byte, 4) for { n1, err := reader.Read(p1) if err == io.EOF { break } log.Printf("[][内容:%v]", string(p1[:n1])) } }
## Writer
- io.Writer 表示一个编写器,它从缓冲区读取数据,并将数据写入目标资源。

- 对于要用作编写器的类型,必须实现 io.Writer 接口的唯一一个方法 Write(p []byte)
- 同样,只要实现了 Write(p []byte) ,那它就是一个编写器。
```go
type Writer interface {
Write(p []byte) (n int, err error)
}
- Write() 方法有两个返回值,一个是写入到目标资源的字节数,一个是发生错误时的错误。
bytes.Buffer库
- bytes.Buffer 的针对的是内存到内存的缓存
closer
- 关闭
type Closer interface { Close() error }
ioutil库 工具包
- ioutil库包含在io目录下,它的主要作用是作为一个工具包,里面有一些比较实用的函数
- 比如 ReadAll(从某个源读取数据)、ReadFile(读取文件内容)、WriteFile(将数据写入文件)、ReadDir(获取目录) ### readFile实例
- 举例 ```go package main
import ( "fmt" "io/ioutil" )
func main() { bytes, err := ioutil.ReadFile("go.mod") if err != nil { fmt.Println(err) return } fmt.Printf("%s", bytes) fmt.Printf("%v", string(bytes)) }
### writeFile 写入文件
- 举例
package main
import ( "fmt" "io/ioutil" )
func main() { fileName := "a.txt" err := ioutil.WriteFile(fileName, []byte("升职加薪\n迎娶白富美"), 0644) fmt.Println(err) }
### readDir 读取目录下的文件元信息
- 举例
```go
package main
import (
"io/ioutil"
"log"
)
func main() {
fs, _ := ioutil.ReadDir("./lugo03")
for _, f := range fs {
log.Printf("[name:%v][size:%v][mode:%v][modTime:%v]",
f.Name(),
f.Size(),
f.Mode(),
f.ModTime(),
)
}
}
os库 操作系统打交道
os.create
- file.WriteString 和 file.Write,举例 ```go package main
import ( "os" )
func main() { file,_:=os.Create("b.txt")
for i:=0;i<5;i++{
file.WriteString("WriteString\n")
file.Write([]byte("Write\n"))
}
}
## os其他常用函数
- 举例
```go
package main
import (
"log"
"os"
)
func main() {
hn, _ := os.Hostname()
log.Printf("主机名:%v", hn)
log.Printf("进程pid:%v", os.Getpid())
log.Printf("命令行参数:%v", os.Args)
log.Printf("获取GOROOT 环境变量:%v", os.Getenv("GOROOT"))
for _, v := range os.Environ() {
log.Printf("环境变量 %v", v)
}
dir,_:=os.Getwd()
log.Printf("当前目录:%v", dir)
log.Println("创建单一config目录")
os.Mkdir("config",0755)
log.Println("创建层级config1/yaml/local目录")
os.MkdirAll("config1/yaml/local",0755)
//log.Printf("删除单一文件或目录",os.Remove("config"))
//log.Printf("删除层级文件或目录",os.RemoveAll("config1"))
}
读取文件 ioutil.ReadFile vs bufio
- 举例
package main
import (
"bufio"
"fmt"
"io/ioutil"
"os"
)
func main() {
// 方式一
bytes1, _ := ioutil.ReadFile("go.mod")
file, _ := os.Open("go.mod")
// 方式二
bytes2, _ := ioutil.ReadAll(file)
// 方法三
file.Close()
file, _ = os.Open("go.mod")
bo := bufio.NewReader(file)
buf := make([]byte, 200)
bo.Read(buf)
fmt.Println(string(bytes1))
fmt.Println(string(bytes2))
fmt.Println(string(buf))
}
- 两者都提供了对文件的读写功能,唯一的不同就是bufio多了一层缓存的功能,这个优势主要体现读取大文件的时候(ioutil.ReadFile是一次性将内容加载到内存,如果内容过大,很容易爆内存)
标准输出,标准输入
- os.Stdout.Write 替代fmt.print
os.stdin 作为脚本的输入内容
测试程序接收os.stdin作为执行脚本
真实生产应用 夜莺监控发送告警,调用python的send.py脚本 ,将发送的内容作为stdin传过去
反射的定义
- 反射是值一类应用,他们能够 字描述 自控制
go中反射的简介
- go是一种静态语言。golang 提供一种机制 。在编译时不知道类型的情况下,可以做如下
- 更新变量
- 运行时查看值
- 调用方法
- 对他们的布局进行操作的机制
为什么使用反射
两个经典场景
- 你编写的函数,还不知道传给你的参数类型是什么,可能是没约定好,也可能是传入的类型很多。
- 希望通过用户的输入来决定调用哪个函数(根据字符串调用方法),动态执行函数
- 举例使用 interface.type判断类型
使用反射获取变量内部的信息
- reflect包提供 valueOf 和TypeOf
- reflect.ValueOf :获取输入接口中的数据的值,如果为空的则返回 0
- reflect.TypeOf :动态获取数据接口中的值的类型,如果为空则返回nil
- 思考为何 TypeOf 可以传入所有的类型,因为所有的类型都实现了空接口
举例1 内置类型的测试
举例2 自定义struct的反射
- 生产使用举例 未知原有类型【遍历探测其Filed】,写个函数统一处理
- go语言里面struct里面成员变量小写,在使用反射时会直接panic
reflect.Value.Interface: cannot return value obtained from unexported field or method
- 但是结构体方法变量小写是不会panic的,也不会反射查看到
- 指针方法是不能被反射查看到的
具体过程
对于成员变量
对于方法
举例3 反射修改值
- 只能指针类型
- pointer.Elem().Setxxx()
举例4 反射调用方法
- 过程说明
- 首先通过reflect.ValueOf 获取到反射类型对象
- reflect.Value.MethodByName这.MethodByName,需要指定准确真实的方法名字,MethodByName代表注册
- []reflect.Value,这个是最终需要调用的方法的参数,无参数传空切片
- 代码举例
结构体标签和反射
- json的标签解析json
- yaml的标签解析yaml
- 自定义xiaoyi标签
- 原理是t.Field.Tag.Lookup("标签名")
- 举例
反射的副作用
1.代码可读性变差
2.隐藏的错误躲过编译检查
- go作为静态语言,编译器能发现类型错误
- 但对于反射代码是无能为力的,可能运行很久才会panic
3. go反射性能问题
type_ := reflect.ValueOf(obj)
fieldValue := type_.FieldByName("hello")
- 每次取出的fieldValue类型是reflect.Value
- 它是一个具体的值,不是一个可复用的反射对象了
- 每次反射都要malloc这个reflect.Value结构体,还有GC
- 比正常代码运行速度慢1-2g个数量级,如果是追求性能的关键模块应减少反射