others linux服务器运维 django3 监控 k8s golang 数据库 大数据 前端 devops 理论基础 java oracle 运维日志

golang 语言基础五

访问量:1017 创建时间:2021-02-18

goroutine channel实现并发和并行(一)

goroutine可以并发执行

需求: 统计1-10000000的数字中哪些是素数,并打印这些素数。 素数: 就是除了1和它本身不能被其他数整出的数。

实现:

  1. 传统方法,通过一个for循环判断各个数是不是素数
  2. 使用并发或者并行方式,将统计素数的任务分配给多个goroutine去完成,这时用到goroutine
  3. goroutine结合channel.

进程、线程以及并行、并发

并行和并发

Golang中的协程(goroutine)以及主线程

Goroutine的使用以及sync.WaitGroup实现主线程等待协程执行完毕

并行执行需求: 在主线程(进程)中,开启一个goroutine,该协程每隔50毫秒输出"你好golang"在主线程中也每隔50毫秒输出"你好golang",输出10次后,退出程序,要求主线程和goroutine同时执行。

package main

import (
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup

func test() {
    for i := 0; i < 10; i++ {
        fmt.Println("test你好golang-", i)
        time.Sleep(time.Millisecond * 100)
    }
    wg.Done() //协程计数器减一
}

func main() {
    wg.Add(1) //协程计数器加1
    go test() //开启一个协程,主线程执行完成后,协程未完成也会随主线程退出
    for i := 0; i < 10; i++ {
        fmt.Println("main你好golang-", i)
        time.Sleep(time.Millisecond * 20)
    }
    wg.Wait() //等待协程都执行完毕
    fmt.Println("主线程退出")
}

启动多个Goroutine

package main

import (
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup

func test() {
    for i := 0; i < 10; i++ {
        fmt.Println("test 你好golang-", i)
        time.Sleep(time.Millisecond * 100)
    }
    wg.Done() //协程计数器减一
}

func test1() {
    for i := 0; i < 10; i++ {
        fmt.Println("test1  你好golang-", i)
        time.Sleep(time.Millisecond * 100)
    }
    wg.Done() //协程计数器减一
}

func main() {
    wg.Add(1) //协程计数器加1
    go test() //开启一个协程,主线程执行完成后,协程未完成也会随主线程退出
    wg.Add(1)
    go test1()
    for i := 0; i < 10; i++ {
        fmt.Println("main 你好golang-", i)
        time.Sleep(time.Millisecond * 20)
    }
    wg.Wait() //等待协程都执行完毕
    fmt.Println("主线程退出")
}

设置Golang 并行运行的时候占用的cup数量

package main
import (
    "fmt"
    "runtime"
)

func main() {
    //获取cpu数量
    cpuNum := runtime.NumCPU()
    fmt.Println("cpunum=", cpuNum)
    //设置使用多个cpu
    runtime.GOMAXPROCS(cpuNum - 1)
    fmt.Println("ok")
}

for循环开启5个协程,每个 协程打印1-5

package main
import (
    "fmt"
    "sync"
)
var wg sync.WaitGroup
func test(num int) {
    defer wg.Done()
    for i := 1; i <= 5; i++ {
        fmt.Println("协程-", num, "第", i)
    }
}
func main() {
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go test(i)
    }
    wg.Wait()
    fmt.Println("关闭主线程...")
}

Goroutine统计素数,统计1-200000之间的素数,统计运行时间

for循环实现

package main

import (
    "fmt"
    "time"
)

func main() {
    start := time.Now().Unix()
    for num := 2; num < 200000; num++ {
        var flag = true
        for i := 2; i < num; i++ {
            if num%i == 0 {
                flag = false
                break
            }
        }
        if flag {
            //fmt.Println(num, "是素数")
        }
    }
    end := time.Now().Unix()
    fmt.Println(end - start)//25秒
}

goroutine实现

package main

import (
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup

func test(n int) {
    for num := (n-1)*50000 + 1; num < n*50000; num++ {
        if num > 1 {
            var flag = true
            for i := 2; i < num; i++ {
                if num%i == 0 {
                    flag = false
                    break
                }
            }
            if flag {
                //fmt.Println(num, "是素数")
            }
        }
    }
    wg.Done()
}

func main() {
    start := time.Now().Unix()
    for i := 1; i <= 4; i++ {
        wg.Add(1)
        go test(i)
    }
    wg.Wait()
    end := time.Now().Unix()
    fmt.Println(end - start) //13秒
}

Channel管道

Channel管道是Go语言在语言级别上提供的goroutine间的通讯方式,可以使用channel在多个goroutine之间传递消息。如果说goroutine是Go程序并发执行体,channel是他们之间的连接,channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。

go语言的并发模型是CSP(Communicatong Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。

go语言中的管道channel是一种特殊的类型。管道像一个传送带或者队列,总是遵循先入先出(First in first out)的规则,保证收发数据的顺序.每一个管道都是一个具体类型的管道,也就是声明channel的时候需要为其指定元素类型。

channel类型

channel是一种引用类型,声明管道类型的格式如下:

var 变量 chan 元素类型
//例子:
var ch1 chan int //声明一个传递整型的管道
var ch2 chan bool//声明一个传递布尔的管道
var ch3 chan []int //声明一个传递int切片的管道

创建channel

声明管道后使用管道,管道必须有容量大小,创建管道格式如下:

make(chan 元素类型,容量)

channel操作 ,管道阻塞

管道有发送send,接受receive和关闭close三种操作 发送和接受都是用<-符号

现在定义一个管道: ch:=make(chan int,3)

发送,将一个值发送到管道中 ch<-10 //把10发送到ch中

接受,从管道取值 x:= <- ch

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 3)
    ch <- 10
    ch <- 21
    ch <- 33
    x := <-ch
    fmt.Println(x) //10
    <-ch           //取值不保存
    c := <-ch
    fmt.Println(c) //33
    //打印管道长度和容量
    fmt.Printf("值%v 容量%v 长度%v\n", ch, cap(ch), len(ch)) //值0xc000018100 容量3 长度0
    //验证管道的类型时引用数据类型
    ch1 := make(chan int, 4)
    ch1 <- 11
    ch1 <- 22
    ch1 <- 33
    ch2 := ch1
    ch2 <- 44
    <-ch1
    <-ch1
    <-ch1
    d := <-ch1
    fmt.Println(d) //44
    //管道阻塞,放不下数据了阻塞,在没有使用协程的情况下,没数据取数据也阻塞
    ch3 := make(chan int, 1)
    ch3 <- 11
    //ch3 <- 22 //fatal error: all goroutines are asleep - deadlock!
}

管道循环遍历 for range从管道循环取值,管道没有key

for val := range ch1 {
    fmt.Println(val)
}

案例:

package main
import (
    "fmt"
)

func main() {
    var ch1 = make(chan int, 10)
    for i := 1; i <= 10; i++ {
        ch1 <- i
    }
    close(ch1) // 关闭管道,不关闭for range会报死锁
    for v := range ch1 {
        fmt.Println(v)
    }
    //for循环管道不用关闭
    var ch2 = make(chan int, 10)
    for i := 1; i <= 10; i++ {
        ch2 <- i
    }
    for j := 0; j < 10; j++ {
        fmt.Println(<-ch2)
    }
}

Goroutine结合Channel管道,一个协程向管道写入数据,一个协程读取管道

package main

import (
    "fmt"
    "sync"
    "time"
)
// 协程与管道协同工作
var wg sync.WaitGroup

func fn1(ch chan int) {
    for i := 1; i <= 10; i++ {
        ch <- i
        fmt.Println("写入数据", i)
        time.Sleep(time.Millisecond * 50)
    }
    close(ch)
    wg.Done()
}

func fn2(ch chan int) {
    for v := range ch {
        fmt.Println("读取数据", v)
    }
    wg.Done()
}

func main() {
    var ch = make(chan int, 10)
    wg.Add(1)
    go fn1(ch)
    wg.Add(1)
    go fn2(ch)
    wg.Wait()
    fmt.Println("主线程退出...")
}

统计素数新的写法:

package main

import (
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup

//向intChan法如1-200000个数
func putNum(intchan chan int) {
    for i := 2; i < 200000; i++ {
        intchan <- i
    }
    close(intchan)
    wg.Done()
}

//从intChan取数,判断是否为素数,是就把素数放入primeChan
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
    for num := range intChan {
        var flag = true
        for i := 2; i < num; i++ {
            if num%i == 0 {
                flag = false
                break
            }
        }
        if flag {
            primeChan <- num //是素数
        }
    }
    wg.Done()
    exitChan <- true
}

//printPrime打印素数的方法
func printPrime(primeChan chan int) {
    //for v := range primeChan {
    //  fmt.Println(v)
    //}
    wg.Done()
}

func main() {
    start := time.Now().Unix()
    intChan := make(chan int, 1000)
    primeChan := make(chan int, 150000) // 队列太小放不下结果
    exitChan := make(chan bool, 16)     // 标识primeChan close
    //存放数字的协程
    wg.Add(1)
    go putNum(intChan)
    //统计素数的协程
    for i := 0; i < 16; i++ {
        wg.Add(1)
        go primeNum(intChan, primeChan, exitChan)
    }
    //打印素数的协程
    wg.Add(1)
    go printPrime(primeChan)

    // 判断exitChan是否存满值
    wg.Add(1)
    go func() { // 匿名自执行函数
        for i := 0; i < 16; i++ {
            <-exitChan
        }
        //关闭primeChan
        close(primeChan)
        wg.Done()
    }()
    wg.Wait()
    end := time.Now().Unix()
    fmt.Println(end - start) //7秒
}

单向管道

有的时候会将管道作为参数在多个任务函数间传递,很多时候在不同任务函数中使用管道都会对其进行限制,比如限制管道在函数中只能发送或者只能接受。

作用在函数参数中规定管道参数只读只写

//默认管道可读可写
// var chan1 chan int //可读可写

//2声明为只写
var chan2 chan <- int
chan2 =make(chan int ,3)
chan2 <-20
// num:= <- chan2 // error

//3.声明为只读
var chan3 <-chan int
num2:=<-chan3
// chan3 <-30 /error

select多路复用

在某些场景下需要同时从多个通道接收数据。这时可以用到select多路复用。通常情况管道在接收数据时,如果没有数据可以接收将会发生阻塞。如下代码所示:

for {
    //尝试从ch1接收值
    data,ok :=<ch1
    //尝试从ch2接收值
    data,ok:=<-ch2
    ..
}

案例:

package main

import "fmt"

func main() {
    intChan := make(chan int, 10)
    for i := 0; i < 10; i++ {
        intChan <- i
    }

    stringChan := make(chan string, 5)
    for i := 0; i < 5; i++ {
        stringChan <- "hello" + fmt.Sprintf("%d", i)
    }
    //使用select获取数据时channel不需要关闭
    for {
        select {
        case v := <-intChan:
            fmt.Printf("从intChan读取数据%d\n", v)
        case v := <-stringChan:
            fmt.Printf("从stringChan读取的数据%d\n", v)
        default:
            fmt.Printf("数据获取完毕\n")
            return //跳出死循环
        }
    }
}

Goroutine Recover解决协程中出现的Panic

package main

import (
    "fmt"
    "time"
)
func sayHello() {
    for i := 0; i < 10; i++ {
        time.Sleep(time.Millisecond * 50)
        fmt.Println("hello,world")
    }
}
func test() {
    //这里可以使用defer+recover
    //定义一个map
    defer func() { //延迟执行匿名自执行函数, 如果不捕获异常,其他协程也无法执行
        //捕获test抛出的panic
        if err := recover(); err != nil {
            fmt.Println("test()发生错误", err)
        }
    }()
    var myMap map[int]string
    myMap[0] = "golang" //没有分配存储空间会报错 panic: assignment to entry in nil map,并且影响其他协程
}

func main() {
    go sayHello()
    go test()
    time.Sleep(time.Second)
}

Golang goroutine 互斥锁 读写互斥锁

互斥锁,是传统并发编程中对共享资源进行访问控制的主要手段,它由标准库sync中的Mutex结构体类型表示。sync.Mutex类型只有2个公开的指针方法,Lock锁定当前共享资源和Unlock解除锁定.

有问题的代码:

package main

import (
    "fmt"
    "sync"
    "time"
)

// go build -race main.go 编译后运行 main.exe ,报错Found 2 data race(s)
var count = 0
var wg sync.WaitGroup

func test() {
    count++
    fmt.Println("the count is : ", count)
    time.Sleep(time.Millisecond)
    wg.Done()
}

func main() {
    for r := 0; r < 20; r++ {
        wg.Add(1)
        go test()
    }
    wg.Wait()
}

加锁后的例子:

package main

import (
    "fmt"
    "sync"
    "time"
)

// go build -race main.go 编译后运行 main.exe ,报错Found 2 data race(s)
var count = 0
var wg sync.WaitGroup
var mutex sync.Mutex // 声明互斥锁

func test() {
    mutex.Lock() //加锁
    count++
    fmt.Println("the count is : ", count)
    time.Sleep(time.Millisecond)
    mutex.Unlock() //解锁
    wg.Done()
}

func main() {
    for r := 0; r < 20; r++ {
        wg.Add(1)
        go test()
    }
    wg.Wait()
}

读写互斥锁sync.RWMutex

当对一个不会变化的数据只做读操作时,不存在资源竞争问题。读写锁可以让多个读操作并发,同时读取,但是对写操作是完全互斥的。也就是说当一个goroutine进行写操作的时候,其他goroutine即不能进行读操作,也不能进行写操作。

package main

import (
    "fmt"
    "sync"
    "time"
)

var wg sync.WaitGroup
var mutex sync.RWMutex

func write() {
    mutex.Lock()
    fmt.Println("执行写操作")
    time.Sleep(time.Second * 2)
    mutex.Unlock()
    wg.Done()
}

func read() {
    mutex.RLock()
    fmt.Println("----执行读操作")
    time.Sleep(time.Second * 2)
    mutex.RUnlock()
    wg.Done()
}

func main() {
    for r := 0; r < 10; r++ {
        wg.Add(1)
        go read()
    }
    for r := 0; r < 10; r++ {
        wg.Add(1)
        go write()
    }
    wg.Wait()
}

反射 反射的引出、反射获取变量类型变量值、反射修改变量值

反射的引子

有时需要写一个函数,这个函数有能力处理各种值类型,而这些类型可能无法共享同一个接口,也可能布局未知,也有可能这个类型在设计函数时还不存在,这时可以用到反射。

空接口可以存储任意类型的变量,如何确定这个接口保存的数据类型和值的方法如下:

反射的基本介绍: 反射是指在程序运行期间对程序本身进行访问和修改的能力。正常情况程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量新的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并有能力修改他们。

反射的作用
反射可以在程序运行期间动态获取变量的各种信息,比如变量的类型 类别
如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、结构体的方法
通过反射,可以修改变量的值,可以调用关联的方法

go语言中变量分为两部分 - 类型信息: 预先定义好的元信息。 - 值信息 : 程序运行过程可动态变化的。

go语言的反射机制,任何接口值都由一个具体类型和具体类型的值两部分组成。 go语言反射功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Type和reflect.Value两部分组成,并且reflect包提供了reflect.TyleOf和reflect.ValueOf两个重要函数来获取任意对象的Value和Type.

reflect.TypeOf()获取任意值的类型对象

package main

import (
    "fmt"
    "reflect"
)

type myInt int
type Person struct {
    Nmae string
    Age  int
}

//反射获取变量类型
func reflectFn(x interface{}) {
    v := reflect.TypeOf(x)
    fmt.Println(v)
}

func main() {
    var a = 10
    var b = 12.3
    c := true
    d := "golang"
    reflectFn(a)
    reflectFn(b)
    reflectFn(c)
    reflectFn(d)
    var e myInt = 111
    var f = Person{
        Nmae: "张三",
        Age:  20,
    }
    reflectFn(e) //main.myInt
    reflectFn(f)
    var h = 25
    reflectFn(&h) //*int
}

tyep Name 和type Kind

在反射中关于类型还划分为两种: 类型Type和种类Kind.因为在Go语言中,可以使用type关键字构造很多自定义类型,而种类Kind就是底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类Kind.举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看他们的类型和种类。

Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空。

package main

import (
    "fmt"
    "reflect"
)

type myInt int
type Person struct {
    Nmae string
    Age  int
}

//反射获取变量类型
func reflectFn(x interface{}) {
    v := reflect.TypeOf(x)
    // v.Name() // 类型名称     使用不多
    // v.Kind() //种类  Kind 就是底层的类型  使用较多
    fmt.Printf("类型:%v  类型名称%v 类型种类%v\n", v, v.Name(), v.Kind())
}

func main() {
    var a = 10
    var b = 12.3
    c := true
    d := "golang"
    reflectFn(a)
    reflectFn(b)
    reflectFn(c)
    reflectFn(d)
    var e myInt = 111
    var f = Person{
        Nmae: "张三",
        Age:  20,
    }
    reflectFn(e) //main.myInt
    reflectFn(f)
    var h = 25
    reflectFn(&h) //*int
    var i = [3]int{1, 2, 3}
    reflectFn(i)
    var j = []int{11, 22, 33}
    reflectFn(j)
}

reflect.ValueOf() 返回的是reflect.Value类型,其中包含了原始值的值信息。 reflect.Value与原始值之间可以互相转换。

package main

import (
    "fmt"
    "reflect"
)

func reflectValue(x interface{}) {
    // var num = 10 + x //10 + x (mismatched types int and interface {})
    // b, _ := x.(int) //通过类型断言实现10+x
    // var num = 10 + b
    //fmt.Println(num)
    //反射实现10+v
    // v := reflect.ValueOf()
    // fmt.Println(v)
    // var num = 10 + v //10 + v (mismatched types int and reflect.Value)
    // fmt.Println(num)

    //反射获取变量的原始值
    v := reflect.ValueOf(x)
    kind := v.Kind()
    switch kind {
    case reflect.Int:
        fmt.Println(v.Int() + 10)
    case reflect.Float32:
        fmt.Println(v.Float() + 10)
    case reflect.String:
        fmt.Println(v.String())
    default:
        fmt.Println("未找到类型")
    }
}

func main() {
    var a = 13
    var b float32 = 1.12
    var c string = "hello"
    reflectValue(a)
    reflectValue(b)
    reflectValue(c)
}

通过反射设置变量的值

package main

import (
    "fmt"
    "reflect"
)

func reflectSetValue(x interface{}) {
    v := reflect.ValueOf(x)
    if v.Elem().Kind() == reflect.Int64 {
        v.Elem().SetInt(100)
    }
}

func main() {
    var a int64 = 130
    reflectSetValue(&a)
    fmt.Println(a)
}

结构体反射详解

结构体反射

package main

import (
    "fmt"
    "reflect"
)

//student结构体

type Student struct {
    Name  string `json:"name1"`
    Age   int    `json:"age"`
    Score int    `json:"score"`
}

func (s Student) GetInfo() string {
    var str = fmt.Sprintf("姓名:%v 年龄:%v 成绩:%v", s.Name, s.Age, s.Score)
    return str
}

func (s *Student) SetInfo(name string, age int, score int) {
    s.Name = name
    s.Age = age
    s.Score = score
}

func (s Student) Print() {
    fmt.Println("这是一个打印方法")
}

//打印字段
func PrintStructField(s interface{}) {
    // 判断参数是不是结构体类型
    t := reflect.TypeOf(s)
    if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
        fmt.Println("传入参数不是结构体")
        return
    }
    // 通过类型变量里面的Field可以获取结构体的字段
    field0 := t.Field(0)
    fmt.Printf("%#v\n", field0) //reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0xa93080), Tag:"json:\"name\"", Offset:0x0, Index:[]int{0}, Anonymous:false}
    fmt.Println(field0)         //{Name  string json:"name" 0 [0] false}
    fmt.Println("字段名称", field0.Name)
    fmt.Println("字段类型", field0.Type)
    fmt.Println("字段tag", field0.Tag.Get("json"))
    // 通过类型变量里面的fieldByName 可以获取结构体的字段
    field1, ok := t.FieldByName("Age")
    if ok {
        fmt.Println("字段名称", field1.Name)
        fmt.Println("字段类型", field1.Type)
        fmt.Println("字段tag", field1.Tag.Get("json"))
    }
    // 通过类型变量里面的NumField获取到该结构体有几个字段
    var fieldCount = t.NumField()
    fmt.Println("结构体有", fieldCount, "个属性")
    //通过值变量获取结构体属性对应的值
    v := reflect.ValueOf(s)
    fmt.Println(v.FieldByName("Name"))
    fmt.Println(v.FieldByName("Age"))
}
func PrintStructFn(s interface{}) {
    // 判断参数是不是结构体类型
    t := reflect.TypeOf(s)
    v := reflect.ValueOf(s)
    if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
        fmt.Println("传入参数不是结构体")
        return
    }
    //1、通过类型变量里面的Method可以获取结构体的方法
    method0 := t.Method(0)    //和结构体方法的顺序没有关系和结构体的ASCII有关系
    fmt.Println(method0.Name) //GetInfo
    fmt.Println(method0.Type)
    //2、通过类型变量获取这个结构体有多少个方法
    method1, ok := t.MethodByName("Print")
    if ok {
        fmt.Println(method1.Name)
        fmt.Println(method1.Type)
    }
    // 3、通过值变量执行方法,注意需要使用值变量,并且要注意参数v.Method(0).Call(nil)
    v.Method(1).Call(nil)
    v.MethodByName("Print").Call(nil)
    info := v.MethodByName("GetInfo").Call(nil)
    fmt.Println(info)
    // 执行方法传入参数,接收的参数是[]reflect.Value的切片
    var params []reflect.Value
    params = append(params, reflect.ValueOf("李四"))
    params = append(params, reflect.ValueOf(23))
    params = append(params, reflect.ValueOf(100))
    v.MethodByName("SetInfo").Call(params)
    info2 := v.MethodByName("GetInfo").Call(nil)
    fmt.Println(info2)
    // 获取方法数量
    fmt.Println(t.NumMethod())
}

func main() {
    stu1 := Student{
        Name:  "小明",
        Age:   12,
        Score: 99,
    }
    PrintStructField(stu1)
    PrintStructFn(&stu1)
}

反射修改结构体属性

package main

import (
    "fmt"
    "reflect"
)

//student结构体

type Student struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Score int    `json:"score"`
}

func (s Student) GetInfo() string {
    var str = fmt.Sprintf("姓名:%v 年龄:%v 成绩:%v", s.Name, s.Age, s.Score)
    return str
}

// 反射修改结构体属性
func reflectChangeStruct(s interface{}) {
    t := reflect.TypeOf(s)
    v := reflect.ValueOf(s)
    if t.Kind() != reflect.Ptr {
        fmt.Println("传入的不是指针类型")
        return
    } else if t.Elem().Kind() != reflect.Struct {
        fmt.Println("传入的不是结构体指针类型")
        return
    }
    name := v.Elem().FieldByName("Name")
    name.SetString("李四")
    age := v.Elem().FieldByName("Age")
    age.SetInt(20)
}

func main() {
    stu1 := Student{
        Name:  "小明",
        Age:   12,
        Score: 99,
    }
    a := 12
    reflectChangeStruct(&a)
    reflectChangeStruct(stu1)
    reflectChangeStruct(&stu1)
    fmt.Println(stu1.GetInfo())
}

6、不要乱用反射

Golang 文件 目录操作

方法一:

// 只读方式打开file,err := os.Open()
//file.Read()
//关闭文件流defer file.Close()
package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    file, err := os.Open("./main.go")
    defer file.Close()
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(file) //&{0xc000072780}
    //读取文件
    var strSlice []byte
    var tempSlice = make([]byte, 128)
    for {
        n, err := file.Read(tempSlice)
        if err == io.EOF {
            fmt.Println("读取完成")
            break
        }
        if err != nil {
            fmt.Println("读取失败")
        }
        fmt.Println("读取字节数量:", n)
        //fmt.Println(string(tempSlice))
        strSlice = append(strSlice, tempSlice[:n]...) //最后一次不一定是128个字节大小
    }
    fmt.Println(string(strSlice))
}

方法二

//bufio读取
//file,err:=os.Open()
//reader := bufio.NewReader(file)
//line,err:= reader.ReadString('\n')
//defer file.Close()
package main
import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {
    file, err := os.Open("./main.go")
    defer file.Close()
    if err != nil {
        fmt.Println(err)
    }

    reader := bufio.NewReader(file)
    for {
        str, err := reader.ReadString('\n') //分隔符\n表示一次读取一行
        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Println(err)
            return
        }
        fmt.Printf(str)
    }
}

方法三

//ioutil一次性读取文件
//ioutil.ReadFile("./main.go")
//
package main
import (
    "fmt"
    "io/ioutil"
)
func main() {
    byteStr, err := ioutil.ReadFile("./main.go")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(byteStr))
}

文件写入方法一

//打开文件file,err:=os.OpenFile("./a.txt",os.O_CREATE|OS.O_RDWR,0666)
//file.Write([]byte(str))写入字节切片数据
//file.WriteString("直接写入字符串数据")//直接写入字符串
//file.Close()

写入方法二

//打开文件file,err:=os.OpenFile("./a.txt",os.O_CREATE|OS.O_RDWR,0666)
//创建write对象 writer := bufio.NewWriter(file)
//将数据写入缓存writer.WriteString("你好\r\n")
// 写缓存到文件 writer.Flush()
//file.Close()

方法三

//str :="hello world"
// err:= ioutil.WriteFile("./a.txt",[]byte(str),0666)
登陆评论: 使用GITHUB登陆