一、文件读写
1.1 os.File
os.File封装所有文件相关操作, 是一个自定义的struct。
a. 打开一个文件进行读操作: os.Open(name string) (*File, error)
Open是以读的方式打开文件(底层调用的是Openfile函数)。
b. 关闭一个文件: File.Close()
1.2 文件操作简单实例
示例1:
package mainimport ("fmt""io" //读取文件末尾特殊情况需要用到"os" //读取文件需要用到 )func main() {filename := "c:/tmp.txt"file, err := os.Open(filename) //返回2个参数,第一个file指针,第二个错误值if err != nil { //如果打开文件有错误,在这里输出fmt.Printf("open %s failed,err:%v\n", filename, err)return}defer func() { //打开一个文件,最后我们必须要将其关闭file.Close()}()var content []byte //定义1个变量存读取到的所有数据var buf [4096]byte //定义一个4k的字节数组,每次读取一点,4k读性能高for {n, err := file.Read(buf[:]) //将整个数组转换成切片读进去,Read函数返回2个参数,第1个n是读到的字节数,第二个是errif err != nil && err != io.EOF { //有一个特殊问题,当一个文件读读完,遇到文件末尾时,它也会返回一个错误,但是此时我已经读到文件末尾EOF,这个错误应该不算错误,所以应该把读到文件末尾这个错误给去掉。fmt.Printf("read %s failed, err:%v\n", filename, err)return //如果有错误就返回 }if err == io.EOF {break //如果读取到文件末尾了,直接break退出。 }vaildBuf := buf[0:n] //把有效数据都拿出来,不可能整个buf数组都是有效数据(最后一次读取到是很大可能是占据数组的一部分。),这里我们就需要借助切片。//fmt.Printf("%s\n", string(vaildBuf))content = append(content, vaildBuf...) //将有效的数据存到定义的变量切片中,另外将一个切片append到另一个切片中用... }fmt.Printf("content:%s\n", content) }
执行结果:
示例2:
通过函数传参
package mainimport ("fmt""io""os" )func Read(filename string) (string, error) {//获得一个filef, err := os.Open(filename)if err != nil {fmt.Println("read fail")return "", err}//把file读取到缓冲区中 defer f.Close()var content []bytevar buf [1024]bytefor {//从file读取到buf中, n表示本次读到的字节数n, err := f.Read(buf[:])if err != nil && err != io.EOF {fmt.Println("read buf fail", err)return "", err}//说明读取结束if err == io.EOF {break}//读取到最终的缓冲区中content = append(content, buf[:n]...)}fmt.Printf("content:%s\n", content)return string(content), nil }func main() {a := "c:/tmp.txt"Read(a) }
执行结果:
1.3 使用bufio提高文件读取性能
之前我们是直接从文件读,因为读取文件是一个相对比较慢的操作,所以当我们读一个大的文件时,或者又多次io操作时会影响性能,所以我们使用bufio(缓存机制,跟redis(缓存)有点类似,可以把文件操作类比成数据库操作)提高文件读取性能。
bufio包go语言已经帮我们封装好了。
bufio包总结如下:
1)bufio 包实现了带缓存的 I/O 操作
2)它封装一个 io.Reader 或 io.Writer 对象
3)使其具有缓存和一些文本读写功能
参考网址:https://blog.csdn.net/wangshubo1989/article/details/70177928
代码示例1:
package mainimport ("bufio""fmt""io""os" )func main() {filename := "c:/tmp.txt"file, err := os.Open(filename) //返回2个参数,第一个file指针,第二个错误值if err != nil { //如果打开文件有错误,在这里输出fmt.Printf("open %s failed,err:%v\n", filename, err)return}defer func() { //打开一个文件,最后我们必须要将其关闭file.Close()}()reader := bufio.NewReader(file) //要封装这个文件读写,需要把文件传到bufio里面去,然后在bufio里面操作这个缓存,这里我们传的是file文件对象//为什么要传一个文件对象进去? 答:首先看缓存里有么有,有就读缓存,没有的话就用文件去读,也就是file这个文件对象(是一个结构体,里面包含很多用法)var content []byte //定义1个变量存读取到的所有数据var buf [4096]byte //定义一个4k的字节数组,每次读取一点,4k读性能高for {n, err := reader.Read(buf[:]) //将整个数组转换成切片读进去,Read函数返回2个参数,第1个n是读到的字节数,第二个是errif err != nil && err != io.EOF { //有一个特殊问题,当一个文件读读完,遇到文件末尾时,它也会返回一个错误,但是此时我已经读到文件末尾EOF,这个错误应该不算错误,所以应该把读到文件末尾这个错误给去掉。fmt.Printf("read %s failed, err:%v\n", filename, err)return //如果有错误就返回 }if err == io.EOF {break //如果读取到文件末尾了,直接break退出。 }vaildBuf := buf[0:n] //把有效数据都拿出来,不可能整个buf数组都是有效数据(最后一次读取到是很大可能是占据数组的一部分。),这里我们就需要借助切片。//fmt.Printf("%s\n", string(vaildBuf))content = append(content, vaildBuf...) //将有效的数据存到定义的变量切片中,另外将一个切片append到另一个切片中用... }fmt.Printf("content:%s\n", content) }
执行结果:
代码示例2:函数传参
package mainimport ("bufio""fmt""io""os" )func Read(filename string) (string, error) {fi, err := os.Open(filename)if err != nil {return "", err}defer fi.Close()r := bufio.NewReader(fi)var content []bytevar buf [1024]bytefor {n, err := r.Read(buf[:])if err != nil && err != io.EOF {return "", err}if err == io.EOF {break}content = append(content, buf[:n]...)}fmt.Printf("content:%s\n", content)return string(content), nil }func main() {a := "c:/tmp.txt"Read(a) }
执行结果:
1.4 使用ioutil读取整个文件
针对小文件读取,十分方便
代码示例:
package mainimport ("fmt""io/ioutil" )func main() {filename := "c:/tmp.txt"content, err := ioutil.ReadFile(filename)if err != nil {fmt.Printf("read file %s failed, err:%v\n", filename, err)return}fmt.Printf("content:%s\n", string(content)) //因为上面返回的是一个字节数组,所以必须转一下 }
执行结果:
代码示例2:使用函数传参
package mainimport ("fmt""io/ioutil" )func Read(filename string) (string, error) {content, err := ioutil.ReadFile(filename)if err != nil {return "", err}fmt.Printf("content:%s\n", content)return string(content), nil }func main() {a := "c:/tmp.txt"Read(a) }
执行结果:
1.5 读取压缩文件示例
gizp就相当于read,gzip使用传进去的文件对象去读取文件内容,解压完了在将数据返回。
下面我们通过几个例子来验证一系列理论
1.5.1 bufio读取压缩文件结果
package mainimport ("bufio""fmt""io""os" )func main() {fmt.Printf("start run...\n")filename := "c:/tmp.txt.gz"file, err := os.Open(filename)if err != nil {fmt.Printf("open %s failed, err:%v\n", filename, err)return}fmt.Printf("start0 read file\n")defer file.Close()reader := bufio.NewReader(file) var content []byte var buf [4096]byte for {n, err := reader.Read(buf[:]) if err != nil && err != io.EOF { fmt.Printf("read %s failed, err:%v\n", filename, err)return}if err == io.EOF {break}vaildBuf := buf[0:n] content = append(content, vaildBuf...) }fmt.Printf("content:%s\n", content) }
执行结果:
解释:
可以看到用bufio读取到的是压缩文件的二进制代码,也是乱码的。
1.5.2 读取压缩文件出现问题
这个问题目前只有读取压缩文件的实例存在,直接读取、bufio、ioutil都不存在这个问题,下面我们通过实例来演示一下。
package mainimport (_ "bufio""compress/gzip" //gzip要用到"fmt""io""os" )func main() {fmt.Printf("start run...\n")filename := "c:/tmp.txt.gz"file, err := os.Open(filename)if err != nil {fmt.Printf("open %s failed, err:%v\n", filename, err)return}fmt.Printf("start0 read file\n")defer file.Close()/*defer func() {file.Close()}()*/reader, err := gzip.NewReader(file) //用gzip构建文件对象,reader就变成了一个读对象if err != nil {fmt.Printf("gzip read failed, err:%v\n", err)return}var content []bytevar buf [100]bytefor {//reader.Readfmt.Printf("start read file\n")n, err := reader.Read(buf[:])fmt.Printf("read %d err:%v\n", n, err)if err != nil && err != io.EOF {fmt.Printf("read %s failed, err:%v\n", filename, err)return}//读到文件末尾了,文件已经读取完毕,Read方法会返回一个io.EOF错误。if err == io.EOF {break}validBuf := buf[0:n]content = append(content, validBuf...)}fmt.Printf("content:%s\n", content) }
执行结果:
解释:
可以发现出现了一个严重问题就是当我们每次读取100字节,分多次读取,会出现最后一次读取不到100字节(是52字节),但是err确是EOF,这个时候按照我们上面代码就会break退出,而此次读取到的52字节也会随之丢失,最终实际的结果也是造成了数据丢失。
1.5.3 读取压缩文件完整代码(解决文件丢失问题)
package mainimport (_ "bufio""compress/gzip" //gzip要用到"fmt""io""os" )func main() {fmt.Printf("start run...\n")filename := "c:/tmp.txt.gz"file, err := os.Open(filename)if err != nil {fmt.Printf("open %s failed, err:%v\n", filename, err)return}fmt.Printf("start0 read file\n")defer file.Close()/*defer func() {file.Close()}()*/reader, err := gzip.NewReader(file) //用gzip构建文件对象,reader就变成了一个读对象if err != nil {fmt.Printf("gzip read failed, err:%v\n", err)return}//reader := bufio.NewReader(file)var content []bytevar buf [100]bytefor {//reader.Readfmt.Printf("start read file\n")n, err := reader.Read(buf[:])fmt.Printf("read %d err:%v\n", n, err)if err != nil && err != io.EOF {fmt.Printf("read %s failed, err:%v\n", filename, err)return}if n > 0 { //做一个判断,只要有字节就追加到储存读取到内容的切片validBuf := buf[0:n]content = append(content, validBuf...)}//读到文件末尾了,文件已经读取完毕,Read方法会返回一个io.EOF错误。if err == io.EOF {break}}fmt.Printf("content:%s\n", content) }
执行结果:
因输出结果太长,此处截图只截取部分内容。
解释:
通过对读取的字节数进行判断,避免了数据丢失的问题。
1.5.4 补充
下面实例有部分用法可能会有疑问,但是写法比较标准,可以学习借鉴。
package mainimport ("bufio""compress/gzip""fmt""os" )func main() {fName := "c:/tmp.txt.gz"var r *bufio.Readerfi, err := os.Open(fName)if err != nil {fmt.Fprintf(os.Stderr, "%v, Can’t open %s: error: %s\n", os.Args[0], fName, err)os.Exit(1)}fz, err := gzip.NewReader(fi)if err != nil {fmt.Fprintf(os.Stderr, "open gzip failed, err: %v\n", err)return}r = bufio.NewReader(fz)for {line, err := r.ReadString('\n')if err != nil {fmt.Println("Done reading file")os.Exit(0)}fmt.Println(line)} }
执行结果:
解释:
先用gizp解压,再用bufio缓存
1.6 文件写入
1) os.OpenFile("filename",os.O_WRONLY|os.O_CREATE,066)
第一个参数是文件名
第二个参数是文件的打开模式:
os.O_WRONLY:只写
os.O_CREATE:创建文件
os.O_RDONLY:只读
os.O_RDWR:读写
os.O_TRUNC:清空
os.O_APPEND:文件存在以追加方式写入
第三个参数:权限控制
r-->4
w-->2
x-->1
2) os.Create("output.dat")
用途:如果文件不存在需要先创建文件,用到的就是os.Create方法。(如果要创建的这个文件本来就存在,还要用Create(底层调用的Openfile函数)方法创建该文件,那么该文件就会被清空)
1.7 文件写入实例
示例1:
package mainimport ("fmt""os" )func isFileExists(filename string) bool { //判断文件是否存在_, err := os.Stat(filename) //os.Stat会返回文件是否存在的相关属性if os.IsNotExist(err) {return false}return true }func main() {filename := "c:/tmp.txt"var file *os.Filevar err errorif isFileExists(filename) {file, err = os.OpenFile(filename, os.O_APPEND, 0755) //如果文件存在则追加进去//file, err = os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0755) //mac系统追加时还需要在加一个os.O_WRONLY方法} else {file, err = os.Create(filename) //不存在就创建该文件 }if err != nil {fmt.Printf("open %s failed, err:%v\n", filename, err)return}defer file.Close()//给文件中写入内容n, err := file.WriteString("hello world") //WriteString可以传入字符串//io.WriteString(file,"hello world") //io.WriteString也可以用来进行传入if err != nil {fmt.Printf("write failed, err:%v\n", err)return}fmt.Printf("write %d succ\n", n) }
执行结果:
查看文件内容:可以发现新追加的也添加进去了。
示例2:
package mainimport ("io""os" )func CheckFileExist(fileName string) bool {_, err := os.Stat(fileName)if os.IsNotExist(err) {return false}return true }func Write(fileName string) error {var f *os.Filevar err errorif CheckFileExist(fileName) { //文件存在f, err = os.OpenFile(fileName, os.O_APPEND, 0666) //打开文件if err != nil {return err}} else { //文件不存在f, err = os.Create(fileName) //创建文件if err != nil {return err}}_, err = io.WriteString(f, "strTest")if err != nil {return err}return nil }func main() {Write("c:/tmp.txt") }
执行结果:
查看文件内容:
示例3:Fprintf
fmt包的Printf底层调用的就是Fprintf
实例如下:
package mainimport ("fmt""os" )func isFileExists(filename string) bool {_, err := os.Stat(filename)if os.IsNotExist(err) {return false}return true }func main() {filename := "c:/tmp.txt"var file *os.Filevar err errorif isFileExists(filename) {//mac机器file, err = os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0755)} else {file, err = os.Create(filename)}if err != nil {fmt.Printf("open %s failed, err:%v\n", filename, err)return}defer file.Close()fmt.Fprintf(file, "%d %d is good", 100, 300) //这里主要是为了演示一下Printf的底层调用Fprintf,我们可以通过传入文件对象,将其写入文件 }
执行结果如下:
查看文件内容:
1.8 使用ioutil直接文件写入
ioutil相当于每次把要写入的内容以覆盖形式写入到文件内
实例1:
package mainimport ("fmt""io/ioutil" )func main() {filename := "c:/tmp.txt"str := "dkfslfjdsklfjlskjflsjflsjflsjflks"err := ioutil.WriteFile(filename, []byte(str), 0755)if err != nil {fmt.Println("write fail")}fmt.Println("write success") }
执行结果:
查看文件内容:
实例2:
package mainimport ("fmt""io/ioutil" )func Write() {fileName := "c:/tmp.txt"strTest := "测试测试"var d = []byte(strTest)err := ioutil.WriteFile(fileName, d, 0666)if err != nil {fmt.Println("write fail")}fmt.Println("write success") }func main() {Write() }
执行结果:
查看文件内容:
1.9 使用bufio进行文件写入
bufio会减少写入文件的次数,先写到缓存中,然后会调用Flush方法将内存中的内容刷新到磁盘中。
bufio写入的缺点:
会造成文件丢失(当要写入的内容还在缓存中,还未被写入到磁盘时,程序挂了,那这些数据就丢失了。)
示例1:
package mainimport ("bufio""fmt""os" )func isFileExists(filename string) bool {_, err := os.Stat(filename)if os.IsNotExist(err) {return false}return true }func main() {filename := "c:/tmp.txt"var file *os.Filevar err errorfile, err = os.Create(filename)if err != nil {fmt.Printf("open %s failed, err:%v\n", filename, err)return}defer file.Close()writer := bufio.NewWriter(file)writer.WriteString("hello worldldfdsfsfsf")writer.Flush() }
执行结果:
查看文件内容:
示例2:
package mainimport ("bufio""fmt""os" )func Write() {fileName := "c:/tmp.txt"f, err3 := os.Create(fileName) //创建文件if err3 != nil {fmt.Println("create file fail")}w := bufio.NewWriter(f) //创建新的 Writer 对象n4, err3 := w.WriteString("bufferedn") //此时还是写入在内存中fmt.Printf("写入 %d 个字节\n", n4)w.Flush() //刷新了磁盘 defer f.Close() }func main() {Write() }
执行结果:
查看文件内容:
1.10 拷贝文件
就相当于一个读另一个写,很简单,核心是io.Copy(其就相当于一个for循环,不断读取源文件内容,然后写入目标文件)
示例如下:
package mainimport ("fmt""io""os" )func main() {CopyFile("c:/tmp.txt", "d:/tmp.txt") //目标文件,源文件fmt.Println("Copy done!") } func CopyFile(dstName, srcName string) (written int64, err error) {src, err := os.Open(srcName) //打开文件if err != nil {return}defer src.Close()dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644) //写入文件if err != nil {return}defer dst.Close()return io.Copy(dst, src) //核心在这里,io.Copy 一行代码搞定 }
执行结果如下:
查看源文件和拷贝文件:
二、终端读写
2.1 终端读写相关介绍
操作终端相关文件句柄常量
os.Stdin:标准输入
os.Stdout:标准输出
os.Stderr:标准错误输出
2.2 终端读写实例
package mainimport ("fmt" )var (firstName, lastName, s stringi intf float32input = "56.12 / 5212 / Go"format = "%f / %d / %s" )func main() {fmt.Println("Please enter your full name: ")fmt.Scanln(&firstName, &lastName)// fmt.Scanf("%s %s", &firstName, &lastName)fmt.Printf("Hi %s %s!\n", firstName, lastName) // Hi Chris Naegelsfmt.Sscanf(input, format, &f, &i, &s)fmt.Println("From the string we read: ", f, i, s) }
执行结果:
2.3 带缓冲区的读写
终端(底层就是一个文件操作)就像1个文件,我们该如何像操作文件一样去操作终端呢?
实例1:
package mainimport ("bufio""fmt""os" )func main() {//标准输出writer := bufio.NewWriter(os.Stdout) //写的话相当于从终端输出,读的话相当于从终端输入writer.WriteString("hello world")writer.Flush() //因为bufio还写在内存中,需要flush一下fmt.Printf("hello world")//标准输入reader := bufio.NewReader(os.Stdin) //从终端读入data, err := reader.ReadString('\n') //readstring方法是传入分隔符,此处传入\n表示直到读入\n为止,也就相当于读一行内容即可if err != nil {fmt.Printf("read from console failed, err:%v\n", err)return}fmt.Fprintf(os.Stdout, "data:%s\n", data) }
执行结果:
实例2:
package mainimport ("bufio""fmt""os" )var inputReader *bufio.Reader var input string var err errorfunc main() {inputReader = bufio.NewReader(os.Stdin)fmt.Println("Please enter some input: ")input, err = inputReader.ReadString('\n')if err == nil {fmt.Printf("The input was: %s\n", input)} }
执行结果:
三、命令行参数
3.1 os.Args
os.Args是一个string的切片,用来存储所有的命令行参数。
package mainimport ("fmt""os" )func main() {fmt.Printf("args count:%d\n", len(os.Args)) //获取当前程序的参数个数for index, v := range os.Args {fmt.Printf("args%d, value:%s\n", index, v)} }
执行结果如下:
3.2 flag包使用
布尔:flag.BoolVar(&test, "b", false, "print on newline")
字符串:flag.StringVar(&str, "s", "", "print on newline")
整型:flag.IntVar(&count, "c", 1001, "print on newline")
flag要传入的参数依次是:传入值-终端参数-默认值-用法
实例:
package mainimport ("flag""fmt" )func main() {var num intvar mode stringflag.IntVar(&num, "num", 16, "-num the passwd length") //IntVar参数依次是:传入值-终端参数-默认值-用法flag.StringVar(&mode, "mode", "mix", "-mode the password generate mode")flag.Parse() //真正解析命令行参数,IntVar、StringVar只是设置命令行需要的一些参数fmt.Printf("num:%d mode:%s\n", num, mode) }
执行结果如下:
可以发现传入的参数直接就被flag包解析了。更牛逼的是连--help都直接帮我们做好了
3.3 命令行参数解析
实例:
package mainimport ("flag" // command line option parser"fmt" )func main() {var test boolvar str stringvar count intflag.BoolVar(&test, "b", false, "print on newline")flag.StringVar(&str, "s", "", "print on newline")flag.IntVar(&count, "c", 1001, "print on newline")flag.Parse()fmt.Println(test)fmt.Println(str)fmt.Println(count) }
执行结果:
3.4 带缓冲区的终端读写
实例:
package mainimport ("bufio""fmt""os" )func main() {fmt.Fprintf(os.Stdout, "%s\n", "hello world! - unbuffered")buf := bufio.NewWriter(os.Stdout)fmt.Fprintf(buf, "%s\n", "hello world! - buffered")buf.Flush() }
执行结果: