《Go语言四十二章经》第三十一章 文件操作与IO

    对于文件和目录的操作,Go主要在os 提供了的相应函数:

    从上面函数定义中我们可以发现一个情况:那就是os中不同函数打开(创建)文件的操作,最终还是通过OpenFile来实现,而OpenFile由编译器根据系统的情况来选择不同的底层功能来实现,对这个实现细节有兴趣可以根据标准包来仔细了解,这里就不展开讲了。

    1. os.Create(name string) 创建新文件,如文件存在则原文件内容会丢失;
    2. os.OpenFile(name string, flag int, perm FileMode) 这个函数可以指定flagFileMode 。这三个函数都会返回一个文件对象。
    1. Flag
    2. O_RDONLY int = syscall.O_RDONLY // 只读打开文件和os.Open()同义
    3. O_WRONLY int = syscall.O_WRONLY // 只写打开文件
    4. O_RDWR int = syscall.O_RDWR // 读写方式打开文件
    5. O_APPEND int = syscall.O_APPEND // 当写的时候使用追加模式到文件末尾
    6. O_CREATE int = syscall.O_CREAT // 如果文件不存在,此案创建
    7. O_EXCL int = syscall.O_EXCL // 和O_CREATE一起使用,只有当文件不存在时才创建
    8. O_SYNC int = syscall.O_SYNC // 以同步I/O方式打开文件,直接写入硬盘
    9. O_TRUNC int = syscall.O_TRUNC // 如果可以的话,当打开文件时先清空文件

    在ioutil包中,也可以对文件操作,主要有下面三个函数:

    1. func ReadFile(filename string) ([]byte, error) // f, err := os.Open(filename)
    2. func WriteFile(filename string, data []byte, perm os.FileMode) error //os.OpenFile
    3. func ReadDir(dirname string) ([]os.FileInfo, error) // f, err := os.Open(dirname)

    这三个函数涉及到了文件IO ,而对文件的操作我们除了打开(创建),关闭外,更主要的是对内容的读写操作上,也即是文件IO处理上。在Go语言中,对于IO的操作在Go 语言很多标准库中存在,很难完整地讲清楚。下面我就尝试结合io, ioutil, bufio这三个标准库,讲一讲这几个标准库在文件IO操作中的具体使用方法。

    Go 语言中,为了方便开发者使用,将 IO 操作封装在了大概如下几个包中:

    • io 为 IO 原语(I/O primitives)提供基本的接口
    • io/ioutil 封装一些实用的 I/O 函数
    • fmt 实现格式化 I/O,类似 C 语言中的 printf 和 scanf ,后面会详细讲解
    • bufio 实现带缓冲I/O

    在 io 包中最重要的是两个接口:Reader 和 Writer 接口。

    有关缓冲:

    • 内核中的缓冲:无论进程是否提供缓冲,内核都是提供缓冲的,系统对磁盘的读写都会提供一个缓冲(内核高速缓冲),将数据写入到块缓冲进行排队,当块缓冲达到一定的量时,才把数据写入磁盘。

    • 进程中的缓冲:是指对输入输出流进行了改进,提供了一个流缓冲,当调用一个函数向磁盘写数据时,先把数据写入缓冲区,当达到某个条件,如流缓冲满了,或刷新流缓冲,这时候才会把数据一次送往内核提供的块缓冲中,再经块化重写入磁盘。

    Go 语言提供了很多读写文件的方式,一般来说常用的有三种。
    一:os.File 实现了Reader 和 Writer 接口,所以在文件对象上,我们可以直接读写文件。

    在使用File.Read读文件时,可考虑使用buffer:

    1. package main
    2. import (
    3. "fmt"
    4. "os"
    5. )
    6. func main() {
    7. b := make([]byte, 1024)
    8. f, err := os.Open("./tt.txt")
    9. _, err = f.Read(b)
    10. f.Close()
    11. if err != nil {
    12. fmt.Println(err)
    13. }
    14. fmt.Println(string(b))
    15. }

    二:ioutil库,没有直接实现Reader 和 Writer 接口,但是通过内部调用,也可读写文件内容:

    1. func ReadFile(filename string) ([]byte, error) //os.Open
    2. func WriteFile(filename string, data []byte, perm os.FileMode) error //os.OpenFile
    3. func ReadDir(dirname string) ([]os.FileInfo, error) // os.Open
    1. type Reader struct {
    2. buf []byte
    3. rd io.Reader // reader provided by the client
    4. r, w int // buf read and write positions
    5. lastByte int
    6. lastRuneSize int
    7. }
    8. type Writer struct {
    9. err error
    10. buf []byte
    11. n int
    12. wr io.Writer
    13. }
    14. func (b *Reader) Read(p []byte) (n int, err error)
    15. func (b *Writer) Write(p []byte) (nn int, err error)

    这三种读方式的效率怎么样呢,我们可以看看:

    经过多次测试,基本上保持 bufio < ioutil < file.Read 这样的成绩, bufio读同一文件耗费时间最少, 效果稳稳地保持在最佳。

    下面代码使用ioutil包实现2种读文件,1种写文件的方法,其中 ioutil.ReadAll 可以读取所有io.Reader 流。所以在网络连接中,也经常使用ioutil.ReadAll 来读取流,后面章节我们会讲到这块内容。

    1. package main
    2. import (
    3. "fmt"
    4. "io/ioutil"
    5. "os"
    6. )
    7. func main() {
    8. fileObj, err := os.Open("./tt.txt")
    9. defer fileObj.Close()
    10. Contents, _ := ioutil.ReadAll(fileObj)
    11. fmt.Println(string(contents))
    12. if contents, _ := ioutil.ReadFile("./tt.txt"); err == nil {
    13. fmt.Println(string(contents))
    14. }
    15. ioutil.WriteFile("./t3.txt", contents, 0666)
    16. }

    bufio 包通过 bufio.NewReader 和bufio.NewWriter 来创建IO 方法集,利用缓冲来处理流,后面章节我们也会讲到这块内容。

    1. package main
    2. import (
    3. "bufio"
    4. "fmt"
    5. "os"
    6. func main() {
    7. fileObj, _ := os.OpenFile("./tt.txt", os.O_RDWR|os.O_CREATE, 0666)
    8. defer fileObj.Close()
    9. Rd := bufio.NewReader(fileObj)
    10. cont, _ := Rd.ReadSlice('#')
    11. fmt.Println(string(cont))
    12. Wr := bufio.NewWriter(fileObj)
    13. Wr.WriteString("WriteString writes a ## string.")
    14. Wr.Flush()
    15. }
    1. 程序输出:
    2. WriteString writes a #

    bufio包中,主要方法如下:

    下面一段代码是,里面有用到peek,Discard 等方法,可以修改方法参数值,仔细体会:

    1. package main
    2. import (
    3. "bufio"
    4. "fmt"
    5. "strings"
    6. )
    7. func main() {
    8. sr := strings.NewReader("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
    9. buf := bufio.NewReaderSize(sr, 0) //默认16
    10. b := make([]byte, 10)
    11. fmt.Println("==", buf.Buffered()) // 0
    12. S, _ := buf.Peek(5)
    13. fmt.Printf("%d == %q\n", buf.Buffered(), s) //
    14. nn, er := buf.Discard(3)
    15. fmt.Println(nn, er)
    16. for n, err := 0, error(nil); err == nil; {
    17. fmt.Printf("Buffered:%d ==Size:%d== n:%d== b[:n] %q == err:%v\n", buf.Buffered(), buf.Size(), n, b[:n], err)
    18. n, err = buf.Read(b)
    19. fmt.Printf("Buffered:%d ==Size:%d== n:%d== b[:n] %q == err: %v == s: %s\n", buf.Buffered(), buf.Size(), n, b[:n], err, s)
    20. }
    21. }