Go语言基础之15--文件基本操作
一、文件读写
1.1 os.File
os.File封装所有文件相关操作, 是一个自定义的struct。
a. 打开一个文件进行读操作: os.Open(name string) (*File, error)
Open是以读的方式打开文件(底层调用的是Openfile函数)。
b. 关闭一个文件: File.Close()
1.2 文件操作简单实例
示例1:
package main import ( "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是读到的字节数,第二个是err if 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 main import ( "fmt" "io" "os" ) func Read(filename string) (string, error) { //获得一个file f, err := os.Open(filename) if err != nil { fmt.Println("read fail") return "", err } //把file读取到缓冲区中 defer f.Close() var content []byte var buf [1024]byte for { //从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 main import ( "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是读到的字节数,第二个是err if 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 main import ( "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 []byte var buf [1024]byte for { 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 main import ( "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 main import ( "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 main import ( "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 main import ( _ "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 []byte var buf [100]byte for { //reader.Read fmt.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 main import ( _ "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 []byte var buf [100]byte for { //reader.Read fmt.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 main import ( "bufio" "compress/gzip" "fmt" "os" ) func main() { fName := "c:/tmp.txt.gz" var r *bufio.Reader fi, 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 main import ( "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.File var err error if 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 main import ( "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.File var err error if 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 main import ( "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.File var err error if 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 main import ( "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 main import ( "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 main import ( "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.File var err error file, 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 main import ( "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 main import ( "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 main import ( "fmt" ) var ( firstName, lastName, s string i int f float32 input = "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 Naegels fmt.Sscanf(input, format, &f, &i, &s) fmt.Println("From the string we read: ", f, i, s) }
执行结果:
2.3 带缓冲区的读写
终端(底层就是一个文件操作)就像1个文件,我们该如何像操作文件一样去操作终端呢?
实例1:
package main import ( "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 main import ( "bufio" "fmt" "os" ) var inputReader *bufio.Reader var input string var err error func 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 main import ( "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 main import ( "flag" "fmt" ) func main() { var num int var mode string flag.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 main import ( "flag" // command line option parser "fmt" ) func main() { var test bool var str string var count int flag.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 main import ( "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() }
执行结果: