go文件
输入流:从文件到go程序内存,读文件的过程
输出流:从go程序内存到文件,写文件的过程
- 打开文件,返回文件指针和错误
file, err := os.Open("文件路径")
- 关闭文件,返回错误信息,经常与defer连用
注意:退出函数时要及时关闭,否则会有内存泄漏
err = os.Close()
- 读取文件
- 带缓存的读取方式,需要打开和关闭文件
reader := bufio.NewReader(file)
str, err := reader.ReadString('\n') //读到换行就结束
- 一次性读取到内存,适用于文件不大的情况
# 不需要打开和关闭文件,被封装在ReadFile
content, err := ioutil.ReadFile(file)
- 文件演示
import (
"os"
"fmt"
"io"
"bufio"
"io/ioutil"
)
func main() {
// open file
file, err := os.Open("c:/test.txt")
if err != nil{
fmt.Println("open file error")
}
fmt.Println(file) //输出文件地址
// close file
defer err = file.Close()
if err != nil{
fmt.Println("close file error")
}
// read file method 1
reader := bufio.NewReader(file)
for { // 直接循环
str, err := reader.ReadString('\n') //读到换行就结束
if err == io.EOF { //读到文件末尾
break
}
}
// read file method 2
file = "d:/test.txt"
content, err := ioutil.ReadFile(file)
if err != nil{
fmt.Println("read file error")
}
fmt.Println(content) //默认是byte[],全是数字
fmt.Println(string(content))
}
- 写文件
import (
"bufio"
)
func main() {
fileName := "c:/test.txt"
file, err := os.OpenFile(fileName , os.O_WRONLY | os.O_CREATE, 666)
if err != nil{
fmt.Println("open file error")
}
str := "Hello Amber!!!\n" //换行,有的编辑器\r\n
writer := bufio.NewWriter(file)
writer.WriteString(str) //此时仅把数据写入缓存
writer.Flush() //将缓存内容写入文件
defer file.Close()
}
os.O_WRONLY | os.O_CREATE :写文件,没有就创建
os.O_WRONLY | os.O_APPEND :写文件,不会覆盖,追加
os.O_RDWR :可读可写
- 文件拷贝
func main() {
file1 = "c:/origin.txt"
file2 = "c:/copy.txt"
data, err := ioutil.ReadFile(file1)
err := ioutil.WriteFile(file2, data, 666)
}
- 判断文件是否存在
func main() {
_, err := os.Stat("c:/origin.txt")
if err == nil{
return true //文件/目录存在
}
if os.IsNotExist(err){
return false //文件/目录不存在
}
}
- 图片/电影拷贝
import (
"io/util"
"bufio"
"os"
)
func CopyFile(dstName string, srcName string) (written int64, err error){
srcFile, err := os.Open(srcName)
if err != nil{
fmt.Println("open file error")
}
reader := bufio.NerReader(srcFile)
defer srcFile.Close()
// Open用于打开读文件,OpenFile是打开写文件
dstFile, err := os.OpenFile(dstName, os.O_WRONLY | os.O_CREATE, 666)
if err != nil{
fmt.Println("open file error")
}
writer := bufio.NewWriter(dstFile)
defer dstFile.Close()
//copy
return io.Copy(writer, reader)
}
func main() {
srcFile := "c:/cat.jpg"
dstFile := "d:/cat.jpg"
_, err := CopyFile(dstFile, srcFile)
if err != nil{
fmt.Println("copy error")
}elsd{
fmt.Println("copy success")
}
}
获取命令行参数
- DOS >>> copy a.txt b.txt
import(
"os"
)
func main() {
for i, val := range os.Args {
fmt.Println("args[%v]=%v", i, val)
}
}
- 通过flag包获取命令行>>> mysql -u root -p root -h localhost
import (
"flag"
)
func main() {
var user string
var pwd string
var host string
var port int
// &user:获取-u后面的输入的参数
//"u":-u指定参数
// "":默认值
//"用户名,默认为空":注释
flag.StringVar(&user, "u", "", "用户名,默认为空")
flag.StringVar(&pwd, "p", "", ",密码默认为空")
flag.StringVar(&host, "h", "localhost", "主机")
flag.StringVar(&port, "port", 3306, "端口")
flag.Parse() //必须进行转换
fmt.Println("user=%v,pwd=%v,host=%v,port=%v", user, pwd, host, port)
}
json
应用场景:网络传输前后台都是以json格式
验证jason格式化网址:www.json.cn
- 序列化:把key-vlue类型的数据如数组,切片序列化成json字符串
import (
"encoding/json"
)
type Student struct {
Name string
Age int
}
func main() {
stu := Student{"amber", 17}
// 序列化,传递的是地址&stu否则无法改变stu的值,默认值拷贝,返回值是byte[]
data, err := json.Marshal(&stu)
if err != nil{
fmt.Println("序列化fail")
}
fmt.Println(string(data))
}
- json反序列化成struct
import (
"encoding/json"
)
type Student struct {
Name string
Age int
}
func main() {
str := "{Name: "amber", "Age": 16}"
var stu Student
// 第一个参数是转换前的[]byte类型,第二个参数是被转换的数据
err := ejson.Unmarshal([]byte(str), &stu)
fmt.Println(stu)
}
go单元测试
控制台:go test -v 带-v代表正确错误都有日志,没有-v正确不会有日志显示在终端
测试单个文件:go test one_test.go
测试单个方法:go test -v -test.run TestUpper
文件名必须以_test.go结尾
函数名必须以Test开头
testing框架:将以test.go文件引入,main函数调用所有以Test开头函数
PASS 代表成功,FAILURE代表失败
import (
"testing" //引入go testing的框架
)
// 函数名必须以Test开头,其后的第一个字母不能是a-z
func TestUpper(t *testing.T) {
res := Upper(10)
if res != 55{
t.Fatalf("error") //停止程序,记录log
}
t.Logf("success")
}
goroutine协程
并发:多线程程序在单核上运行
并行:多线程程序在多核上运行
- 特点
- 有独立的栈空间
- 共享程序堆空间
- 调度有用户控制
- 协程是轻量级的线程
func test() {
for i :=0; i < 10; i++{
fmt.Println("test hello world")
}
}
func main() {
go test() //开启协程,主线程和test交叉执行
for i :=0; i < 10; i++{
fmt.Println("main hello world")
}
}
遇到go关键字时,主线程会再开出一个分支执行go后面的方法,主线程仍继续执行,若主线程执行完毕,分支线程无论是否结束都要结束
主线程比较耗费资源,分支线程是轻量级的
- MPG模式
M:操作系统的主线程
P:协程上下文环境(程序运行所需的资源,比如cpu分配)
G:协程
举个🐾:现有ABCD四个客人点菜,A点了10个炒菜,厨师做菜的时候BCD就一直等着,但当厨师在做炖汤的时候,可以把B的菜先炒了,计算机可以来回在A和B之间切换
import "runtime"
func main() {
cpuNum := runtime.NumCPU()
fmt.Printf("电脑上CPU个数=%v", cpuNum)
//设置使用的CPU个数
runtime.GOMAXPROCS(cpuNum - 1)
}
Question🐣
当多个协程同时对数据库写入时,会报错
解决方案:1,管道,2,互斥锁
import "sync"
//lock时全局互斥锁,Mutex是结构体,里面有lock和unlock2个方法
var lock sync.Mutex
//在执行写入操作之前
lock.lock()
map[0] = res
//在执行完写入操作之后
lock.Unlock()
go管道channel
特点:
- 先进先出【FIFO】
- 线程安全,无需加锁
声明:var 变量名 chan 数据类型
初始化:make
func main() {
var intChan chan int
// make创建存放3个int类型的管道
intChan = make(chan int, 3)
fmt.println(intChan) //输出的是地址,&intChan也是地址
//向管道写入数据
intChan<- 100
num := 200
intChan<- num
fmt.println(len(intChan), cap(intChan))
// 注意,make时指定的容量就是3,如果写入超过3个会报错
// 取出数据,若数据已全部被取出还在取会报错
var num2 int
num2 = <-intChan
// 仅把数据扔出,不给任何变量
<-intChan
}
取出的数据类型也是interface,需要断言
- 管道的关闭
var intChan chan int
intChan = make(chan int, 3)
intChan<- 100
close(intChan) //关闭管道
intChan<- 200 //报错,管道关闭不允许写入
n:= <-intChan //可以正常读取
- 遍历管道
①:遍历时,如果channel没有关闭,会出现deadlock错误
②:遍历时,如果channel关闭,正常遍历
注意:遍历管道时不能使用len(),因为每取出一个数据,管道的长度是会变的
intchan2 := make(chan int, 100)
for i:=0;i<100;i++ {
intchan2<- i
}
close(intchan2) //关闭
for i range intchan2 {
fmt.Println(i) //直接取出
}
- 协程和管道结合
问题描述:定义2个协程,一个写入50个数据,一个读出50个数据,并且保证在主进程结束之前完成读写操作
思路:为防止主线程在协程之前结束,需要在定义一个管道专门用来放读完的标志
func write(intChan chan int) {
for i :=1;i <=50;i++{
intChan <- i
}
close(intChan)
}
func read(intChan chan int, boolChan chan bool) {
for {
val, ok := <- intChan
if !ok {
fmt.Println("read data=%v", val)
}
}
boolChan <- true
close(boolChan)
}
func main() {
intChan := make(chan int, 50)
exitChan := make(chan bool, 1)
go write(intChan)
go read(intChan, exitChan)
for {
_, ok := <- exitChan
if !ok {
break
}
}
}
- 管道阻塞
不管读的速度快还是写的速度块都不会发生死锁,因为编译器会分析
for {
select { //如果管道没有关闭,也可以遍历数据,不会被阻塞,若管道里没有数据自动匹配下一个case
case v := <-intChan:
fmt.Println(v)
case v := <-stringChan:
fmt.Println(v)
default :
break //只能跳到select,无法跳出for循环
return // 可以
}
}
①:管道声明为双向 var 管道名 chan int
②:管道声明为只写 var 管道名 chan <- int
③:管道声明为只读 var 管道名 <- chan int