31.2 IO读写

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

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

在 io 包中最重要的是两个接口:Reader 和 Writer 接口。这两个接口是我们了解整个I/O的关键,我们只要记住:实现了这两个接口,就有了 I/O 的功能

有关缓冲:

  • 内核中的缓冲:无论进程是否提供缓冲,内核都是提供缓冲的,系统对磁盘的读写都会提供一个缓冲(内核高速缓冲),将数据写入到块缓冲进行排队,当块缓冲达到一定的量时,才把数据写入磁盘。
  • 进程中的缓冲:是指对输入输出流进行了改进,提供了一个流缓冲,当调用一个函数向磁盘写数据时,先把数据写入缓冲区,当达到某个条件,如流缓冲满了,或刷新流缓冲,这时候才会把数据一次送往内核提供的块缓冲中,再经块缓冲写入磁盘。

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

func (f *File) Read(b []byte) (n int, err error)
func (f *File) Write(b []byte) (n int, err error)

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

package main
import (
	"fmt"
	"os"
)
func main() {
	b := make([]byte, 1024)
	f, err := os.Open("./tt.txt")
	_, err = f.Read(b)
	f.Close()
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(b))
}

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

func ReadAll(r io.Reader) ([]byte, error) 
func ReadFile(filename string) ([]byte, error)  //os.Open
func WriteFile(filename string, data []byte, perm os.FileMode) error  //os.OpenFile
func ReadDir(dirname string) ([]os.FileInfo, error)  // os.Open

三:使用bufio库,这个库实现了I/O的缓冲操作,通过内嵌io.Reader、io.Writer接口,新建了Reader ,Writer 结构体。同时也实现了Reader 和 Writer 接口。

type Reader struct {
	buf          []byte
	rd           io.Reader // reader provided by the client
	r, w         int       // buf read and write positions
	err          error
	lastByte     int
	lastRuneSize int
}
type Writer struct {
	err error
	buf []byte
	n   int
	wr  io.Writer
}
func (b *Reader) Read(p []byte) (n int, err error) 
func (b *Writer) Write(p []byte) (nn int, err error) 

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

package main
import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"time"
)
func read1(path string) {
	fi, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer fi.Close()
	buf := make([]byte, 1024)
	for {
		n, err := fi.Read(buf)
		if err != nil && err != io.EOF {
			panic(err)
		}
		if 0 == n {
			break
		}
	}
}
func read2(path string) {
	fi, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer fi.Close()
	r := bufio.NewReader(fi)
	buf := make([]byte, 1024)
	for {
		n, err := r.Read(buf)
		if err != nil && err != io.EOF {
			panic(err)
		}
		if 0 == n {
			break
		}
	}
}
func read3(path string) {
	fi, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer fi.Close()
	_, err = ioutil.ReadAll(fi)
}
func main() {
	file := "" //找一个大的文件,如日志文件
	start := time.Now()
	read1(file)
	t1 := time.Now()
	fmt.Printf("Cost time %v\n", t1.Sub(start))
	read2(file)
	t2 := time.Now()
	fmt.Printf("Cost time %v\n", t2.Sub(t1))
	read3(file)
	t3 := time.Now()
	fmt.Printf("Cost time %v\n", t3.Sub(t2))
}

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