goroutine可以并发执行
需求: 统计1-10000000的数字中哪些是素数,并打印这些素数。 素数: 就是除了1和它本身不能被其他数整出的数。
实现:
进程、线程以及并行、并发
并行和并发
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管道是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多路复用。通常情况管道在接收数据时,如果没有数据可以接收将会发生阻塞。如下代码所示:
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)
}
互斥锁,是传统并发编程中对共享资源进行访问控制的主要手段,它由标准库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、不要乱用反射
方法一:
// 只读方式打开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)