1.介绍

bufio包实现了有缓冲的I/O。它包装一个io.Readerio.Writer接口对象,使用这个包可以大幅提高文件读写的效率。

1.如何提高效率?

image-20210408135753993

1.写入流程梳理:

  • 当写入内容小于缓冲区(buf)的可用大小时,内容写入缓冲区(buf);
  • 当缓冲区(buf)空间不够时,一次性将缓冲区(buf)内容写入文件,并清空缓冲区(buf);
  • 当写入内容大于缓冲区(buf)空间时,将内容直接写入文件;

2.读取流程梳理:

  • 当读取内容小于缓冲区(buf)空间时,从缓冲区(buf)读取;
  • 当缓冲区(buf)内容为空时,一次性从文件中读取大小等于缓冲区(buf)的内容;
  • 当读取内容大于缓冲区(buf)空间时,直接读取文件;

总结:   避免频繁操作文件、减少访问本地磁盘次数,从而提高效率。

2.读取(Read*)

2.1 创建读取器

1. 创建默认缓冲大小 (NewReader)

a.签名

1
func NewReader(rd io.Reader) *Reader

根据rd创建一个具有默认大小缓冲的*Reader。默认大小:4096字节

b.使用

1
2
3
reader := bufio.NewReader(strings.NewReader("hello Word"))
fmt.Printf("默认大小:%v",reader.Size())
// 输出: 默认大小:4096

2. 创建指定缓冲大小( NewReaderSize)

1.签名

1
func NewReaderSize(rd io.Reader, size int) *Reader

根据rd创建指定size大小缓冲的*Reader。最小大小为16字节

2.使用

1
2
3
4
5
6
7
8
reader := bufio.NewReaderSize(strings.NewReader("hello Word"),40)
fmt.Printf("大于16字节:%v\n",reader.Size())
reader2 := bufio.NewReaderSize(strings.NewReader("hello Word"),4)
fmt.Printf("小于16字节:%v\n",reader2.Size())
/**输出
大于16字节:40
小于16字节:16
*/

2.2 读取指定字节(Read)

1.签名

1
func (b *Reader) Read(p []byte) (n int, err error)

将读取数据写入p。返回写入p的字节数和错误信息。当读取到达结尾时,返回值n将为0而err将为io.EOF

2.使用

现有文件a.txt,内容如下:

1
2
3
4
张三 23 北京 男
李四 22 南京 男
郭明 32 上海 男
王英 18 武汉 女

读取程序如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func main() {
	//打开一个文件
	file, err := os.Open("./a.txt")
	if err != nil {
		fmt.Println(err)
		return
	}
	// 关闭文件
	defer file.Close()
	// 创建读取器
	reader := bufio.NewReader(file)
	for  {
		// 一次读取21个字节
		context := make([]byte, 21)
		_, err = reader.Read(context)
		// 读取完毕,则跳出
		if err == io.EOF {
			break
		}
		fmt.Printf("读取内容:%s", context)
	}
}
/*输出
读取内容:张三 23 北京 男
读取内容:李四 22 南京 男
读取内容:郭明 32 上海 男
读取内容:王英 18 武汉 女
*/

2.3 读取一个字节(ReadByte)

1.签名

1
func (b *Reader) ReadByte() (byte, error)

每次读取一个字节返回。当读取到达结尾时,返回值n将为0而err将为io.EOF

2.使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
	// 基于字符串创建读取器
	reader := bufio.NewReader(strings.NewReader("Go,Word!"))
	for  {
		// 一次读取1个字节
		readByte, err := reader.ReadByte()
		// 读取完毕,则跳出
		if err == io.EOF {
			break
		}
		fmt.Printf("读取内容:%s\n", string(readByte))
	}
}
/**输出
读取内容:G
读取内容:o
读取内容:,
读取内容:W
读取内容:o
读取内容:r
读取内容:d
读取内容:!
*/

2.4 读取一行(ReadLine)

1.签名

1
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)

ReadLine尝试读取返回一行数据,不包括行尾标志的字节,但是有以下几个场景需要注意:

  • 如果行太长超过了缓冲,返回值isPrefix=true,Line为行的前一部分。该行剩下部分将在下一次读取中返回并且此时的isPrefix=false,isPrefix会在行的最后一个片段时才设为false
  • 如果行内小于等于缓冲,isPrefix=false,Line为一整行的内容。

官方文档中推荐使用 ReadBytes('\n')ReadString('\n')来代替ReadLine

2.当行超过了缓冲大小时

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func main() {
	// 创建字符串,每行超过16个字节
	str := strings.Repeat("Hello,Hello,Hello,Hello!\n",3)
	// 基于字符串创建一个缓冲区=16字节的读取器
	reader := bufio.NewReaderSize(strings.NewReader(str),16)
	for  {
		// 每次读取一行
		line, prefix, err := reader.ReadLine()
		// 读取完毕,则跳出
		if err == io.EOF {
			break
		}
		fmt.Printf("line:%s isPrefix:%t \n", line,prefix)
	}
}

输出:

image-20210410172327103

3.当行小于缓冲大小时

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func main() {
	// 每行不超过16个字节
	str := strings.Repeat("Hello Word!\n",3)
	// 基于字符串创建读取器
	reader := bufio.NewReaderSize(strings.NewReader(str),16)
	for  {
		// 每次读取一行
		line, prefix, err := reader.ReadLine()
		// 读取完毕,则跳出
		if err == io.EOF {
			break
		}
		fmt.Printf("line:%s isPrefix:%t \n", line,prefix)
	}
}
/**输出
line:Hello Word! isPrefix:false 
line:Hello Word! isPrefix:false 
line:Hello Word! isPrefix:false 
*/

2.5 读取到指定的字符

1.同类方法列表

方法 delim是否报错
ReadSlice(delim byte) (line []byte, err error) 报错(bufio: buffer full)
ReadBytes(delim byte) ([]byte, error) 不会
ReadString(delim byte) (string, error) 不会

每次读取到字符delim(包含)

2.常规使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
	// 基于字符串创建读取器
	reader := bufio.NewReaderSize(strings.NewReader("Go,PHP,Java,Python,C"),16)
	for  {
		// 读取
		s, err := reader.ReadString(',')
		// 读取完毕,则跳出
		if err == io.EOF {
			break
		} else if err != nil{
			fmt.Printf("err:%s",err.Error())
			break
		}
		fmt.Printf("%s\n", s)
	}
}
/**输出
Go,
PHP,
Java,
Python,
*/

上述例子中C后面没有,,所以不会读出

3.模拟每次读取一行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
	// 设置每行超过缓冲区大小
	repeat := strings.Repeat("Hello,Hello,Hello,Hello\n",3)
	// 基于字符串创建读取器
	reader := bufio.NewReaderSize(strings.NewReader(repeat),16)
	for  {
		// 读取
		s, err := reader.ReadString('\n')
		// 读取完毕,则跳出
		if err == io.EOF {
			break
		} else if err != nil{
			fmt.Printf("err:%s",err.Error())
			break
		}
		fmt.Printf("%s", s)
	}
}
/** 输出
Hello,Hello,Hello,Hello
Hello,Hello,Hello,Hello
Hello,Hello,Hello,Hello
*/

虽然每行超过缓冲区大小,但是依然可以整行读出

2.6 每次读取前几个字节(Peek)

1.签名

1
func (b *Reader) Peek(n int) ([]byte, error)

每次读取输入流的前n个字节,且不会移动读取位置,所以当输入流不变时,每次读取都是一样的。

  • 当内容小于n时,error会返回io.EOF
  • n大于缓冲区大小时,error会返回ErrBufferFull

2.内容小于n

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func main() {
    // 基于字符串创建读取器
    reader := bufio.NewReaderSize(strings.NewReader("hello"),16)
    // 读取
    for i := 0; i < 2; i++ {
        s, err := reader.Peek(16)
        fmt.Printf("结果: %s ", s)
        if err == io.EOF {
            fmt.Printf("%s","已读完")
            break
        } else if err != nil{
            fmt.Printf("err:%s",err.Error())
            break
        }
    }
}
// 输出-> 结果: hello 已读完

3.当n大于缓冲区大小时

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func main() {
	// 基于字符串创建读取器
	reader := bufio.NewReaderSize(strings.NewReader("hello"),16)
	// 读取
	for i := 0; i < 2; i++ {
		// 大于缓冲区大小
		s, err := reader.Peek(17)
		fmt.Printf("结果: %s ", s)
		if err == io.EOF {
			fmt.Printf("%s","已读完")
			break
		} else if err != nil{
			fmt.Printf("err:%s",err.Error())
			break
		}
	}
}
// 输出-> 结果: hello err:bufio: buffer full

3.写入(Write*)

3.1 创建写入器

1.创建默认缓冲大小(NewWriter)

1
func NewWriter(w io.Writer) *Writer

根据w创建一个具有默认大小缓冲的*Writer。默认大小:4096字节

2.创建指定缓冲大小(NewWriter)

1
func NewWriterSize(w io.Writer, size int) *Writer 

3.2 Write*

1.方法列表

方法名 描述
Write(p []byte) (nn int, err error) Writep的内容写入缓冲。返回写入的字节数。
如果返回值nn < len(p),还会返回一个错误说明原因。
WriteString(s string) (int, error) 同上,只是入参变成字符串。

2.当写入内容大于缓冲区时

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func main() {
	file, _ := os.OpenFile("./test.txt",os.O_APPEND|os.O_WRONLY,os.ModePerm)
	defer file.Close()
	reader := bufio.NewWriterSize(file,20)
	// 当写入的内容字节大于缓冲区大小时,会直接写入文件
	write, err := reader.Write([]byte("hello,hello,hello 你好!"))
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("直接写入文件: %d \n",write)
}
// 输出——> 直接写入文件: 27

3.当写入内容小于缓冲区时

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func main() {
	file, _ := os.OpenFile("./test.txt",os.O_APPEND|os.O_WRONLY,os.ModePerm)
	defer file.Close()
	reader := bufio.NewWriterSize(file,20)
	// 当写入的内容字节小于缓冲区大小时,不会直接写入文件
	write, err := reader.Write([]byte("\nhello,Go!"))
	if err != nil {
		fmt.Println(err)
		return
	}
	// 该函数会使缓冲区的内容,直接写入文件
	_ = reader.Flush()
	fmt.Printf("Flush写入文件: %d \n",write)
}
// 输出-> Flush写入文件: 10