【go学习笔记四】接口、模拟OOP、并发编程、标准库


golang 接口

接口是一种新的类型定义,把所有具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

要实现接口,需要实现接口的所有方法。

语法格式

// USB接口
type USB interface {
    read()
    write()
}

type Computer struct {
    name string
}

func (c Computer) read() {
    fmt.Printf("Computer %v reading\n", c.name)
}

func (c Computer) write() {
    fmt.Printf("Computer %v writing\n", c.name)
}

func main() {
    var c USB = Computer{
        name: "yadi",
    }
    c.read()
    c.write()
    fmt.Printf("c: %#v\n", c) // c: main.Computer{name:"yadi"}
}

值类型接收者和指针类型接收者

传拷贝和传指针的区别,拷贝不影响原值,指针影响原值。

接口和类型的关系

  • 一个类型可以实现多个接口

    type PlayerMusic interface {
        playMusic()
    }
    
    type PlayVideo interface {
        playVideo()
    }
    
    type Mobile struct {
    }
    
    func (m Mobile) playMusic() {
        fmt.Println("play music")
    }
    
    func (m Mobile) playVideo() {
        fmt.Println("play video")
    }
    
    func main() {
        m := Mobile{}
        m.playMusic()
        m.playVideo()
    }
    
  • 多个类型可以实现同一个接口(多态)

    type Pet interface {
        eat()
    }
    
    type Dog struct {
    }
    
    type Cat struct {
    }
    
    func (d Dog) eat() {
        fmt.Println("dog eat")
    }
    
    func (c Cat) eat() {
        fmt.Println("cat eat")
    }
    
    func main() {
        var p Pet
        p = Dog{}
        p.eat() // dog eat
        p = Cat{}
        p.eat() // cat eat
    }
    

接口嵌套


type Flyer interface {
    fly()
}

type Swimmer interface {
    swim()
}

// 接口的组合
type FlyFish interface {
    Flyer
    Swimmer
}

type FFish struct {
}

func (f FFish) fly() {
    fmt.Println("fish fly")
}

func (f FFish) swim() {
    fmt.Println("fish swim")
}

func main() {
    var ff FlyFish = FFish{}
    ff.fly()  // fish fly
    ff.swim() // fish swim
}

OCP设计原则

Open-closed Principle。对扩展开放,对修改关闭。

如Pet接口,Dog和Cat结构体实现了Pet接口,要增加Pig结构体,也可直接继承Pet接口,不造成对源代码的修改。

golang模拟OOP属性和方法

可以通过结构体struct和函数绑定来实现OOP的属性和方法等特性。OOP:面向对象编程。

继承

通过结构体嵌套实现继承

type Animal struct {
}

type Dog struct {
    a Animal
}

构造函数

使用函数模拟构造函数的功能。

type Person struct {
    name string
    age  int
}

func NewPerson(name string, age int) (*Person, error) {
    if name == "" || age < 0 {
        return nil, fmt.Errorf("args wrong")
    }
    return &Person{name: name, age: age}, nil
}

func main() {
    person, err := NewPerson("", 15)
    fmt.Printf("err: %#v\n", err)       // err: &errors.errorString{s:"args wrong"}
    fmt.Printf("person: %#v\n", person) // person: (*main.Person)(nil)

    fmt.Println("----------------")

    person, err = NewPerson("tom", 15)
    fmt.Printf("err: %#v\n", err)       // err: <nil>
    fmt.Printf("person: %#v\n", person) // person: &main.Person{name:"tom", age:15}
}

包管理工具go module

创建一个包,一般是创建一个文件夹,在文件夹里的go文件中,使用package关键字声明包名称。

go module是golang 1.11新加的特性,用来管理模块中的包依赖关系

go在依赖管理时,会创建两个文件,go.modgo.sum

go.mod提供了依赖版本的全部信息。

go.mod如:

module go_pro

go 1.18

require github.com/go-sql-driver/mysql v1.6.0 // indirect

go.sum如:

github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=

格式就是:

依赖的路径 版本号 hash算法

这两个文件暂时先了解这么多。

使用方法

  • 初始化模块:go mod init <项目模块名称>

  • 依赖关系处理,根据go.mod文件:

    go mod tidy需要依赖就下载,没有用到就去掉

  • 将依赖包复制到项目下的vendor目录:

    go mod vendor

  • 显示依赖关系

    go list -m all

  • 显示详细依赖关系

    go list -m -json all

  • 下载依赖

    go mod download [path@version]

安装gin:

Gin 是 Go语言写的一个 web 框架,它具有运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json。

> go get -u github.com/gin-gonic/gin

之后,go.mod会自动加入需要的依赖:

module go_pro

go 1.18

require (
    github.com/gin-contrib/sse v0.1.0 // indirect
    github.com/gin-gonic/gin v1.7.7 // indirect
    github.com/go-playground/locales v0.14.0 // indirect
    github.com/go-playground/universal-translator v0.18.0 // indirect
    github.com/go-playground/validator/v10 v10.10.1 // indirect
    github.com/go-sql-driver/mysql v1.6.0 // indirect
    github.com/golang/protobuf v1.5.2 // indirect
    github.com/json-iterator/go v1.1.12 // indirect
    github.com/leodido/go-urn v1.2.1 // indirect
    github.com/mattn/go-isatty v0.0.14 // indirect
    github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
    github.com/modern-go/reflect2 v1.0.2 // indirect
    github.com/ugorji/go/codec v1.2.7 // indirect
    golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
    golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
    golang.org/x/text v0.3.7 // indirect
    google.golang.org/protobuf v1.28.0 // indirect
    gopkg.in/yaml.v2 v2.4.0 // indirect
)

demo:

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(ctx *gin.Context) {
        ctx.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run()
}

结果:

image-20220415122559519

golang并发编程

协程

go task()

demo

import (
    "fmt"
    "time"
)

func show(msg string) {
    for i := 0; i < 5; i++ {
        fmt.Printf("msg: %v\n", msg)
        time.Sleep(time.Millisecond * 100)
    }
}

普通函数运行:

func main() {
    show("Go")
    show("Hi")
    // msg: Go
    // msg: Go
    // msg: Go
    // msg: Hi
    // msg: Hi
    // msg: Hi
}

其中一个以协程方式执行:

func main() {
    go show("Go") // go 启动了一个协程来执行
    show("Hi")
    // msg: Hi
    // msg: Go
    // msg: Hi
    // msg: Go
    // msg: Go
    // msg: Hi
}

两个都以协程方式执行:

func main() {
    go show("Go") // go 启动了一个协程来执行
    go show("Hi")
    // 什么都没输出,因为还没开始打印main函数已经退出了
}

可以在main最后等待一段时间:

func main() {
    go show("Go")
    go show("Hi")
    time.Sleep(time.Second * 2)
    // msg: Hi
    // msg: Go
    // msg: Go
    // msg: Hi
    // msg: Hi
    // msg: Go
}

channel

通道,在goroutine之间共享数据。

需要在声明通道时指定数据类型。在任何给定时间只有一个协程可以访问数据项。

两种通道:

  • 无缓冲,用于协程之间的同步通信,保证发送和接收瞬间执行两个协程的交换。
  • 缓冲,异步通信。
unBufferd := make(chan int) // 整型无缓冲通道
buffered := make(chan int, 10) //整型有缓冲通道

发送值和接收值:

unBufferd <- 4
data := <-unBufferd
// 当然,不能再一个协程里写,无缓冲通道会死锁
// fatal error: all goroutines are asleep - deadlock!
fmt.Printf("data: %v\n", data)

有缓冲就没问题了:

buffered <- 4
data := <-buffered
fmt.Printf("data: %v\n", data) // data: 4

完整的小例子测试:

var values = make(chan int)

func send() {
    rand.Seed(time.Now().UnixNano())
    value := rand.Intn(10)
    fmt.Printf("send: %v\n", value)
    time.Sleep(time.Second * 5)
    values <- value
}

func main() {
    defer close(values) // 主函数退出的函数把通道关了
    go send() // 启动一个协程
    fmt.Println("waiting...")
    value := <-values
    fmt.Printf("value: %v\n", value)
    fmt.Println("end...")
}

// waiting...
// send: 7 阻塞几秒
// value: 7
// end...

waitGroup同步

sync.WaitGroup内部维护一个计数,初始值为0。

保证在并发环境中完成指定数量的任务。

没有同步情况:

import (
    "fmt"
    "time"
)

func show(msg string) {
    for i := 0; i < 5; i++ {
        fmt.Printf("msg: %v\n", msg)
        time.Sleep(time.Millisecond * 100)
    }
}

func main() {
    go show("Go") 
    go show("Hi")
    // 什么都没输出,因为还没开始打印main函数已经退出了
}

同步情况:

var wg sync.WaitGroup

func show(msg string) {
    defer wg.Done()
    for i := 0; i < 3; i++ {
        fmt.Printf("msg: %v\n", msg)
        time.Sleep(time.Millisecond * 100)
    }
}

func main() {
    wg.Add(1)
    go show("Go")
    wg.Add(1)
    go show("Hi")

    wg.Wait()
    fmt.Println("end...")
}

// msg: Hi
// msg: Go
// msg: Go
// msg: Hi
// msg: Hi
// msg: Go
// end...

runtime包

定义了一些协程管理相关的API

runtime.Gosched()

让出CPU时间片,重新等待安排任务

例子:

func show(msg string) {
    for i := 0; i < 2; i++ {
        fmt.Printf("msg: %v\n", msg)
    }
}

func main() {
    go show("Go")
    for i := 0; i < 2; i++ {
        runtime.Gosched() // 让出时间片
        fmt.Println("golang....")
    }
    fmt.Println("end...")
}

runtime.Goexit()

退出当前协程。

runtime.GOMAXPROCS

设置能最多运行的CPU核心数。

fmt.Printf("runtime.NumCPU(): %v\n", runtime.NumCPU())
runtime.GOMAXPROCS(2)

Mutex互斥锁

测试:

var value int = 0

func add() {
    // 当次数小的时候,出现问题的概率小,当大的时候,如十万,百万,最后的结果就出现了问题
    for i := 0; i < 100000; i++ {
        value += 1
    }
}

func main() {
    go add()
    go add()
    go add()
    time.Sleep(1 * time.Second)
    fmt.Printf("value: %v\n", value)
    // value: 127431
    // value: 117896
}

加锁之后:

var value int = 0
var value_lock sync.Mutex

func add() {
    for i := 0; i < 100000; i++ {
        value_lock.Lock()
        value += 1
        value_lock.Unlock()
    }
}

func main() {
    go add()
    go add()
    go add()
    time.Sleep(1 * time.Second)
    fmt.Printf("value: %v\n", value)
    // value: 300000
}

channel遍历

通道关闭读到默认值,通道未关闭,读的多了,死锁情况如下:

import "fmt"

var c = make(chan int)

func main() {
    go func() {
        for i := 0; i < 2; i++ {
            c <- i
        }
        close(c)
    }()

    r := <-c
    fmt.Printf("r: %v\n", r)
    r = <-c
    fmt.Printf("r: %v\n", r)
    r = <-c // channel已经关闭了,读到0;未关闭,死锁
    fmt.Printf("r: %v\n", r)
}

fori循环遍历:

for i := 0; i < 2; i++ {
    r := <-c
    fmt.Printf("r: %v\n", r)
}

for range:

for v := range c {
    fmt.Printf("v: %v\n", v)
}

循环判断状态:

for {
    v, ok := <-c
    if ok {
        fmt.Printf("v: %v\n", v)
    } else {
        break
    }
}

select switch

select 会监听case语句中channel的读写操作,当case中的channel读写操作为非阻塞状态,会触发相应的动作。

select中case语句必须是一个channel操作

select中的default子句总是可运行的

如果多个case可写,随机公平地选一个执行,其他不会执行。

如果没有可运行的case语句,且有default,就执行default。

如果没有可执行的case语句,且没有default,就阻塞。

demo

var chanInt = make(chan int, 0)
var chanStr = make(chan string)

func main() {
    go func() {
        chanInt <- 1
        chanStr <- "Hi"
        defer close(chanInt)
        defer close(chanStr)
    }()

    for {
        select {
        case r := <-chanInt:
            fmt.Printf("chanInt: %v\n", r)
        case r := <-chanStr:
            fmt.Printf("chanStr: %v\n", r)
        default:
            fmt.Println("waiting...")
        }
        time.Sleep(time.Second * 1)
    }
    // chanInt: 1
    // chanStr: Hi
    // chanStr: 
    // chanStr: 
    // chanInt: 0
    // chanStr: 
    // chanStr: 
    // chanInt: 0
    // chanInt: 0
}

Timer

内部也是通过管道实现的。

demo1:

func main() {
    timer := time.NewTimer(time.Second * 2)
    fmt.Printf("time.Now(): %v\n", time.Now())

    t1 := <-timer.C // C是channel, 阻塞,直至时间到了
    fmt.Printf("t1: %v\n", t1)
    // time.Now(): 2022-04-18 13:10:11.9222806 +0800 CST m=+0.003442901
    // t1: 2022-04-18 13:10:13.9367907 +0800 CST m=+2.017953001
}

demo2:

func main() {
    fmt.Printf("time.Now(): %v\n", time.Now())
    <-time.After(time.Second * 2)
    fmt.Printf("time.Now(): %v\n", time.Now())
    // time.Now(): 2022-04-18 13:14:36.3144857 +0800 CST m=+0.003912601
    // time.Now(): 2022-04-18 13:14:38.329183 +0800 CST m=+2.018609901
}

time.After和time.Sleep区别

func main() {
    fmt.Printf("time.Now()1: %v\n", time.Now())
    t := time.After(time.Second * 2)
    fmt.Printf("time.Now()2: %v\n", time.Now())
    time.Sleep(time.Second * 5)
    fmt.Printf("(<-t)3: %v\n", (<-t))
    // time.Now()1: 2022-04-18 13:18:47.8695158 +0800 CST m=+0.003094101
    // time.Now()2: 2022-04-18 13:18:47.8834527 +0800 CST m=+0.017031001
    // (<-t)3: 2022-04-18 13:18:49.8910131 +0800 CST m=+2.024591401
}

Sleep就阻塞在那,After非阻塞,等到从通道里取值的时候才:没到时间就阻塞,否则就立刻返回。

停止计时器

func main() {
    timer := time.NewTimer(time.Second * 1)
    go func() {
        <-timer.C
        fmt.Println("time up...")
    }()
    timer.Stop()  // 取消计时器
    fmt.Println("time stop...")
    time.Sleep(time.Second * 3)
}

time Reset重新设置时间。

Ticker

Timer只执行一次,Ticker周期执行。

简单使用:

func main() {
    ticker := time.NewTicker(time.Second)
    counter := 0
    for _ = range ticker.C {
        counter++
        fmt.Println("Ticker...") // 1s周期运行
        if counter == 5 {
            ticker.Stop()
            break
        }
    }
}

和通道一起使用:

func main() {
    chanInt := make(chan int)

    ticker := time.NewTicker(time.Second)
    go func() {
        for _ = range ticker.C {
            select {
            case chanInt <- 1:
            case chanInt <- 2:
            case chanInt <- 3:
            }
        }
    }()

    sum := 0
    for v := range chanInt {
        fmt.Printf("v: %v\n", v)
        sum += v
        if sum >= 10 {
            break
        }
    }
}

// v: 2
// v: 3
// v: 1
// v: 3
// v: 1

原子变量

对一个简单的变量实现同步,一种方法是加锁,另一种方式就是使用原子变量。

原子变量计数器:

var wg sync.WaitGroup
var v int32 = 0

// CAS操作

func add() {
    defer wg.Done()
    atomic.AddInt32(&v, 1)
}

func main() {
    for i := 0; i < 100000; i++ {
        wg.Add(1)
        go add()
    }
    wg.Wait()
    fmt.Printf("v: %v\n", v)
}

其他操作(也适用于其他类型):

atomic.LoadInt32(&i)
atomic.StoreInt32(&i,100)
atomic.CompareAndSwapInt32(&i,100,101)

标准库

os模块

文件目录相关

创建文件:

func createFile() {
    // 没有就创建文件,有就覆盖
    f, err := os.Create("a.txt")
    if err != nil {
        fmt.Printf("err: %v\n", err)
    } else {
        // 打印文件名称
        fmt.Printf("f.Name(): %v\n", f.Name())
    }
}

创建目录:

func makeDir() {
    err := os.Mkdir("a", os.ModePerm)
    // 创建多级目录
    err2 := os.MkdirAll("a/b/c", os.ModePerm)
    if err != nil {
        fmt.Printf("err: %v\n", err)
    }
    if err2 != nil {
        fmt.Printf("err2: %v\n", err2)
    }
}

删除文件或目录:

func remove() {
    err := os.Remove("a.txt")
    if err != nil {
        fmt.Printf("err: %v\n", err)
    }

    // 删除目录
    err2 := os.RemoveAll("a")
    if err2 != nil {
        fmt.Printf("err2: %v\n", err2)
    }
}

查看和切换当前目录:

func wd() {
    dir, _ := os.Getwd()
    fmt.Printf("dir: %v\n", dir) // dir: C:\Users\PC\Desktop\go_pro
    os.Chdir("d:\\")

    dir, _ = os.Getwd()
    fmt.Printf("dir: %v\n", dir) // dir: d:\
}

临时目录:

os.TempDir()

重命名文件:

os.Rename

简单读写文件:

func readFile() {
    b, _ := os.ReadFile("a.txt")
    fmt.Printf("b: %v\n", string(b[:]))
}

func writeFile() {
    _ = os.WriteFile("a.txt", []byte("Hello world"), os.ModePerm)
}

File文件读操作

读文件

func openClose() {
    // 只读打开
    f, err := os.Open("a.txt")
    if err != nil {
        fmt.Printf("err: %v\n", err)
    }
    var b []byte = make([]byte, 20)
    f.Read(b)
    fmt.Printf("b: %v\n", string(b[:])) // b: Hello world
    f.Close()
}

OpenFile可以进行更细致的一些操作:

func openClose() {
    f, err := os.OpenFile("a.txt", os.O_RDWR|os.O_CREATE, 0755)
    if err != nil {
        fmt.Printf("err: %v\n", err)
    } else {
        fmt.Printf("f.Name(): %v\n", f.Name())
        f.Close()
    }
}

持续读取,直至文件尾:

func openClose() {
    buf := make([]byte, 3)
    f, _ := os.Open("a.txt")
    for {
        n, err := f.Read(buf)
        if err == io.EOF {
            break
        }
        if n < 3 {
            buf[2] = 0
        }
        fmt.Printf("string(buf): %v\n", string(buf))
    }
}

// string(buf): Hel
// string(buf): lo 
// string(buf): wor
// string(buf): ld

读取子目录:

image-20220418162838891

简单读取:

func openClose() {
    de, _ := os.ReadDir("a")
    for _, v := range de {
        fmt.Printf("v.IsDir(): %v\n", v.IsDir())
        fmt.Printf("v.Name(): %v\n", v.Name())
    }
    // v.IsDir(): true
    // v.Name(): b
    // v.IsDir(): true
    // v.Name(): d
}

文件树的形式:

image-20220418163920992

func openClose(str string, prefix string) {
    de, err := os.ReadDir(str)
    if err != nil {
        fmt.Printf("err: %v\n", err)
        return
    }
    for _, v := range de {
        if !v.IsDir() {
            fmt.Printf("%v%v%v\n", prefix[:maxInt(0, len(prefix)-4)], "|-- ", v.Name())
        } else {
            fmt.Printf("%v%v\n", prefix, v.Name())
            openClose(str+"/"+v.Name(), prefix+"    ")
        }
    }
}

File文件写操作

func write() {
    f, err := os.OpenFile("a.txt", os.O_APPEND|os.O_CREATE, 0755)
    if err != nil {
        fmt.Printf("err: %v\n", err)
        return
    }
    f.Write([]byte("Hello world!"))
    f.WriteString("\nanother Line")
    f.Close()
}

进程相关操作

func main() {
    // 获取当前进程的进程id
    fmt.Printf("os.Getpid(): %v\n", os.Getpid())
    // 获取父进程id
    fmt.Printf("os.Getppid(): %v\n", os.Getppid())

    // 设置新进程属性
    attr := &os.ProcAttr{
        Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
        Env:   os.Environ(),
    }

    // 开启新进程
    p, err := os.StartProcess("C:\\Program Files (x86)\\Notepad++\\notepad++.exe", []string{"C:\\Program Files (x86)\\Notepad++\\notepad++.exe"}, attr)
    if err != nil {
        fmt.Printf("err: %v\n", err)
        return
    }
    fmt.Printf("p: %v\n", p)
    fmt.Printf("p.Pid: %v\n", p.Pid)

    // 根据进程id找进程
    p2, _ := os.FindProcess(p.Pid)
    fmt.Printf("p2: %v\n", p2)
    // 设置10s后执行一个函数
    time.AfterFunc(time.Second*10, func() {
        p.Signal(os.Kill)
    })

    ps, _ := p.Wait()
    fmt.Printf("ps: %v\n", ps)
}

os包和环境相关的方法

获取所有环境变量:os.Environ()

获取某个环境变量:os.Getenv()

查找某个环境变量:os.LookupEnv()

io模块

  • io:提供基本的接口
  • io/ioutil封装一些使用的io函数
  • fmt,io格式化
  • bufio 实现带缓冲的io

基本接口

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

demo1:

func main() {
    r := strings.NewReader("hello world")
    buf := make([]byte, 10)
    r.Read(buf)
    fmt.Printf("string(buf): %v\n", string(buf)) // string(buf): hello worl
}

demo2:

copy到输出控制台。

r := strings.NewReader("hello world")
if _, err := io.Copy(os.Stdout, r); err != nil {
    log.Fatal(err)
}
// hello world

带缓冲区的复制:

r := strings.NewReader("hello world")
buf := make([]byte, 20)
// 带缓冲区复制,效率更高
if _, err := io.CopyBuffer(os.Stdout, r, buf); err != nil {
    log.Fatal(err)
}
// hello world

多个reader:

r1 := strings.NewReader("first reader ")
r2 := strings.NewReader("second reader ")
r3 := strings.NewReader("third reader\n")

r := io.MultiReader(r1, r2, r3)

buf := make([]byte, 20)
// 带缓冲区复制,效率更高
if _, err := io.CopyBuffer(os.Stdout, r, buf); err != nil {
    log.Fatal(err)
}
// first reader second reader third reader

管道

io.pipe()

ioutil包

读字符串reader:

r := strings.NewReader("hi hihihi hi")
b, err := ioutil.ReadAll(r)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("string(b): %v\n", string(b)) // string(b): hi hihihi hi

读文件reader:

f, _ := os.Open("a.txt")
b, err := ioutil.ReadAll(f)

ioutil.ReadDir()

func main() {
    fi, err := ioutil.ReadDir(".")
    if err != nil {
        fmt.Printf("err: %v\n", err)
        return
    }
    for _, v := range fi {
        fmt.Printf("%v\n", v.Name())
    }
    // a
    // a.txt
    // go.mod
    // go.sum
    // main.go
    // user
}

ioutil.ReadFile()

b, err := ioutil.ReadFile("a.txt")

writeFile()

// 覆盖原内容
ioutil.WriteFile("a.txt", []byte("ioutil writefile"), 0755)

临时文件:

content := []byte("temp file content")
tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
    log.Fatal(err)
}

defer os.Remove(tmpfile.Name())

fmt.Printf("tmpfile.Name(): %v\n", tmpfile.Name())
// tmpfile.Name(): C:\Users\PC\AppData\Local\Temp\example1840979046
if _, err := tmpfile.Write(content); err != nil {
    log.Fatal(err)
}
tmpfile.Close()

bufio

多了一个缓冲:

type Reader struct {
    buf          []byte
    rd           io.Reader // reader provided by the client
    r, w         int       // buf read and write positions
    err          error
    lastByte     int // last byte read for UnreadByte; -1 means invalid
    lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
}

例子:

func main() {
    r := strings.NewReader("ddddddddddfasdf")
    r2 := bufio.NewReader(r)
    s, _ := r2.ReadString('\n') 
    fmt.Printf("s: %v\n", s) // s: ddddddddddfasdf
}

rune 英[ruːn]

非英文字符读取:

func main() {
    r := strings.NewReader("我的博客")
    r2 := bufio.NewReader(r)

    r3, size, _ := r2.ReadRune()
    fmt.Printf("%c  %v\n", r3, size)
    r3, size, _ = r2.ReadRune()
    fmt.Printf("%c  %v\n", r3, size)
    r3, size, _ = r2.ReadRune()
    fmt.Printf("%c  %v\n", r3, size)
    r3, size, _ = r2.ReadRune()
    fmt.Printf("%c  %v\n", r3, size)
    r3, size, _ = r2.ReadRune()
    fmt.Printf("%c  %v\n", r3, size)
    // 我  3
    // 的  3
    // 博  3
    // 客  3
    // 0
}

Scanner

r := strings.NewReader("ABC DEF GHI JKL")
s := bufio.NewScanner(r)
s.Split(bufio.ScanWords)
for s.Scan() {
    fmt.Printf("%v\n", s.Text())
}
// ABC
// DEF
// GHI
// JKL

log

image-20220418210300629

panic:还是会执行defer

func main() {
    defer fmt.Println("panic之后再执行")
    log.Panic("发生了panic异常")
    fmt.Println("end...")
}
// 2022/04/18 21:12:22 发生了panic异常
// panic之后再执行
// panic: 发生了panic异常

// goroutine 1 [running]:
// log.Panic({0xc000107f30?, 0x60?, 0xff7be0?})
//         D:/go1.18/src/log/log.go:385 +0x65
// main.main()
//         c:/Users/PC/Desktop/go_pro/main.go:10 +0x94
// exit status 2

配置

func main() {
    log.Print(log.Flags())
    log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
    log.Print(log.Flags())
    // 2022/04/18 21:17:18 3
    // 2022/04/18 21:17:18 main.go:10: 19
}

整合

搞成一个初始化函数:

func init() {
    log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
    log.SetPrefix("mylog: ")
    f, _ := os.OpenFile("a.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
    log.SetOutput(f)
}

func main() {
    log.Print("dns")
    log.Print("haha")
    log.Panic("123")
}

image-20220418212925958

自定义logger

var diLogger *log.Logger

func init() {
    f, _ := os.OpenFile("a.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
    diLogger = log.New(f, "Mylog: ", log.Ldate|log.Ltime|log.Lshortfile)
}

func main() {
    diLogger.Print("dns")
    log.Print("haha") // 二者的输出互不影响
    diLogger.Panic("123")
}

builtin

内建,不需要导入,里面的变量和函数可以直接使用。

一些函数

s := []int{}
// 添加元素
s = append(s, 100)
fmt.Printf("s: %v\n", s) // s: [100]

// 获取字符串、切片、数组长度
len := len(s)
fmt.Printf("len: %v\n", len)

print("tom", " ", 20, "\n")

// 抛出一个panic异常, 程序退出,defer还会照常执行
panic("something wrong...")

newmake区别:

  • make只能用来分配和初始化类型为slicemapchan的数据,new可以分配任意类型的数据。
  • new返回的是指针,make返回本身
  • new分配的空间会被清零,make分配后会进行初始化