golang bufio包怎么用?

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,企业版 4核16GB
推荐场景:
HTAP混合负载
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生内存数据库 Tair,内存型 2GB
简介: `bufio` 是 Go 语言中用于提高 I/O 性能的包,它通过使用缓冲区减少对低效磁盘 I/O 操作的调用。简而言之,`bufio` 提供带缓冲的读写功能,减少读取或写入文件时的系统调用次数,从而提升程序性能。

bufio

它的作用用一句话表述就是:

利用缓冲区减少io操作次数,提升读写性能。

1. 为什么要用bufio?

开始之前我们先来看一段代码:

go

复制代码

package main

import (
	"fmt"
	"io"
	"os"
)

func main() {
	// 读取当前目录 data.txt文件内容
	file, err := os.Open("./data.txt")
	if err != nil {
		fmt.Println("打开文件错误:", err)
		return
	}
	defer file.Close()

	data := make([]byte, 3)
	// 读取10次 每次读取3个字节
	for i := 0; i < 10; i++ {
		_, err := file.Read(data)

		// 遇到文件结束
		if err == io.EOF {
			fmt.Println(err)
			break
		}
		fmt.Println(string(data))
	}
}

上面实现了一个简单的文件读取功能,能正常工作,但是有一个有一个问题,每次从文件读取3个字节,而且读取了10次,也就是读取了3 * 10 = 30个字节的数据,却做了10次io操作,性能可想而知。

那么我们如何优化呢? 请出我们的主角bufio,它的主要作用是:减少io操作次数,提供读写性能

我们用bufio优化下

go

复制代码

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	// 读取当前目录 data.txt文件内容
	file, err := os.Open("./data.txt")
	if err != nil {
		fmt.Println("打开文件错误:", err)
		return
	}
	defer file.Close()

	// 用bufio封装一层 返回一个reader
	reader := bufio.NewReader(file)

	data := make([]byte, 3)
	// 读取10次 每次读取3个字节
	for i := 0; i < 10; i++ {
		_, err := reader.Read(data) // 这里改成从reader中读

		// 遇到文件结束
		if err == io.EOF {
			fmt.Println(err)
			break
		}
		fmt.Println(string(data))
	}
}

优化很简单总共两步:

  1. bufio封装一层返回一个reader
  2. bufio.Reader去替换原来的直接文件(io.Reader)读

2. bufio缓冲区读写原理

首先bufio的主要对象是缓冲区,操作主要有两个:

记住,它底层的所有东西都围绕读、写展开。

原理上,我们也按照读、写来分别说明:

PS: 下面流程只是一个大概参考,不代表全部逻辑

lua

复制代码

 
 读取长度小于缓冲区大小,从缓冲区读取
 1.----------------->
                    当缓冲区为空,直接从文件读取,填满缓冲区
                    2. -------------->
 【程序】           【缓冲区】           【文件(io.Reader)】
  
  3. 读取长度超过缓冲区大小,直接从文件读取
  ----------------------------------> 

lua

复制代码

 写长度小于缓冲大小,先写入缓冲区
 1.----------------->
                    当缓冲区满,触发写入到文件
                    2. -------------->
 【程序】           【缓冲区】           【文件(io.Reader)】
  
  3. 写长度超过缓冲区大小,直接写入文件
  -----------------------------------> 

在bufio内部实现的reader和writer,大致是按照上述逻辑处理的,还有些细节的东西,没有在上面画出,但是做为初学者,了解下就行。

3. bufio读

在介绍之前,先说明一点,无论是读还是写,其构造过程都是差不多的:

  1. NewReader/NewWriter构造一个读/写对象
  2. 传入一个实现了io.Reader/io.Writer的对象

1. 构造bufio读对象

只要是实现了io.Reader对象都可以,比如:

go

复制代码

// =================1.从文件==============
file, err := os.Open("./data.txt")
if err != nil {
fmt.Println("打开文件错误:", err)
return
}
defer file.Close()

reader := bufio.NewReader(file)

// =================2. 从字符串=========
strReader := strings.NewReader("hello world")
bufio.NewReader(strReader)

// =================3. 从网络链接=======
bufio.NewReader(conn)

这里就不一一列举了。

2. Read读

和直接从原始对象读一样

go

复制代码

package main

import (
	"bufio"
	"fmt"
	"strings"
)

func main() {
	strReader := strings.NewReader("hello world")
	buf := bufio.NewReader(strReader)

	// 读前要构造一个切片 用于存放读取的内容
	data := make([]byte, 5)
	// 读取数据到data
	_, err := buf.Read(data)

	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(string(data)) // 转字符串打印
}

// hello

3. ReadLine 按照行读取

有两点需要注意:

  1. 它返回三个参数 line、isPrefix、err
  2. 如果一行太长本次没读取完,则isPrefix会是true
  3. 返回的文本不包括行尾("\r\n"或"\n")

ps: 官方更推荐使用ReadString/ReadBytes/Scaner

go

复制代码

package main

import (
	"bufio"
	"fmt"
	"io"
	"strings"
)

func main() {
	str := `
	 大家好
	 非常好
	 非常非常好
	`
	strReader := strings.NewReader(str)
	buf := bufio.NewReader(strReader)

	for {
		// 返回三个参数 line、是否前缀、错误
		line, _, err := buf.ReadLine()
		// 结束直接返回
		if err == io.EOF {
			fmt.Println("结束啦")
			break
		}

		// 字符串直接打印
		fmt.Println(string(line))
	}
}

// 大家好
// 非常好
// 非常非常好
// 结束啦

4. ReadString 直接读出字符串

它有两个好处:

  1. 直接返回字符串,省得转换
  2. 不用事先构造一个切片来装读取到的数据

注意它读取后的内容里是包含分割符号的

go

复制代码

package main

import (
	"bufio"
	"fmt"
	"io"
	"strings"
)

func main() {
	str := `
	 大家好
	 非常好
	 非常非常好
	`
	strReader := strings.NewReader(str)
	buf := bufio.NewReader(strReader)

	for {
		// 这里是一个分割符
		s, err := buf.ReadString('\n')
		// 结束直接返回
		if err == io.EOF {
			fmt.Println("结束啦")
			break
		}

		// 字符串直接打印
		fmt.Printf(s)
	}
}

// 大家好
// 非常好
// 非常非常好
// 结束啦

这里还有几个类似的方法,非常接近,就不单独演示了 区别在于,ReadBytes 它返回一个字节切片([]byte)

5. Scanner 扫描

特点:

  1. 自己定义一个扫描函数,然后按照规则扫描;如果不指定扫描器,它和单独按照行读取类型;
  2. 返回内容不包含换行符

go

复制代码

package main

import (
	"bufio"
	"fmt"
	"strings"
)

func main() {
	str := `
	 大家好
	 非常好
	 非常非常好
	`
	strReader := strings.NewReader(str)
	// 先生成一个Scanner
	scanner := bufio.NewScanner(strReader)

	// 扫描每行
	for scanner.Scan() {
		// 返回的是一个字符串
		content := scanner.Text()
		fmt.Println(content)
	}

	// 检查扫描过程是否报错
	if err := scanner.Err(); err != nil {
		fmt.Println("扫描过程发生了错误:", err.Error())
	}
}

4. bufio 写

缓冲区默认大小为4K(4096字节) 这里需要注意的是,如果缓冲区没有满,不会自动写入io; 我们可以手动Flush 完成写入

先看下代码:

go

复制代码

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {

	// os.O_RDWR|os.O_CREATE 读写 如果不存在则创建
	file, err := os.OpenFile("data.txt", os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		fmt.Println(err)
		return
	}

	defer file.Close()
	// 构造缓冲写
	buf := bufio.NewWriter(file)

	// 三次write写入缓冲
	buf.Write([]byte("hello world\n"))
	buf.Write([]byte("非常美丽\n"))
	buf.Write([]byte("不错吧\n"))

	// 直接写入文件
	buf.Flush()
}

1. 构造writer

go

复制代码

//直接用io.Writer构造
buf := bufio.NewWriter(file)

// 指定缓冲大小 (最小是16字节)
buf := bufio.NewWriterSize(file, 30)

2. 各种wirter方式

主要有以下几种方式:

go

复制代码

// 以字符串方式写入
buf.WriteString("来吧来吧来\n")
	
// 一次写一个rune字符 返回实际占用的字节数
n, _ := buf.WriteRune('中')
c, _ := buf.WriteRune('\n')

// 一次写入一个byte
buf.WriteByte('a')
buf.WriteByte('A')

3. Flush写入io

go

复制代码

// 直接写入io
buf.Flush()

4. 其它

go

复制代码

// 重置buf 此前缓冲中的数据都被清理掉 
buf.Reset(os.Stdout)

// 缓冲区大小(总大小)
buf.Size()
// 缓冲区可用大小
buf.Available()


转载来源:https://juejin.cn/post/7304923130605109284

相关文章
|
8月前
|
监控 网络协议 Go
Golang抓包:实现网络数据包捕获与分析
Golang抓包:实现网络数据包捕获与分析
|
19天前
|
SQL NoSQL Go
技术经验分享:Golang标准库:errors包应用
技术经验分享:Golang标准库:errors包应用
14 0
|
2月前
|
XML JSON Go
Golang深入浅出之-XML处理在Go语言中的实现:encoding/xml包
【4月更文挑战第26天】Go语言的`encoding/xml`库提供XML处理,包括序列化和反序列化。本文讨论了XML处理的基础,如`xml.Marshal`和`xml.Unmarshal`函数,以及常见问题和易错点,如标签命名、结构体嵌套、omitempty标签和命名空间。建议遵循标签命名规则,正确处理嵌套和属性,谨慎使用omitempty,以及理解并有效利用命名空间。文中还给出了基础示例和处理XML属性的代码示例,帮助读者掌握XML处理技巧。
42 1
Golang深入浅出之-XML处理在Go语言中的实现:encoding/xml包
|
2月前
|
监控 Go 开发者
Golang深入浅出之-Goroutine泄漏检测与避免:pprof与debug包
【5月更文挑战第2天】本文介绍了Go语言并发编程中可能遇到的Goroutine泄漏问题,以及如何使用`pprof`和`debug`包来检测和防止这种泄漏。常见的问题包括忘记关闭channel和无限制创建goroutine。检测方法包括启动pprof服务器以监控Goroutine数量,使用`debug.Stack()`检查堆栈,以及确保每个Goroutine有明确的结束条件。通过这些手段,开发者可以有效管理Goroutine,维持程序性能。
109 7
|
2月前
|
Java Go
Golang深入浅出之-Goroutine泄漏检测与避免:pprof与debug包
【5月更文挑战第1天】本文介绍了Go语言中goroutine泄漏的问题及其影响,列举了忘记关闭通道、无限循环和依赖外部条件等常见泄漏原因。通过引入`net/http/pprof`和`runtime/debug`包,可以检测和避免goroutine泄漏。使用pprof的HTTP服务器查看goroutine堆栈,利用`debug`包的`SetGCPercent`和`FreeOSMemory`函数管理内存。实践中,应使用`sync.WaitGroup`、避免无限循环和及时关闭通道来防止泄漏。理解这些工具和策略对维护Go程序的稳定性至关重要。
72 4
|
2月前
|
安全 测试技术 Go
Golang深入浅出之-Go语言单元测试与基准测试:testing包详解
【4月更文挑战第27天】Go语言的`testing`包是单元测试和基准测试的核心,简化了测试流程并鼓励编写高质量测试代码。本文介绍了测试文件命名规范、常用断言方法,以及如何进行基准测试。同时,讨论了测试中常见的问题,如状态干扰、并发同步、依赖外部服务和测试覆盖率低,并提出了相应的避免策略,包括使用`t.Cleanup`、`t.Parallel()`、模拟对象和检查覆盖率。良好的测试实践能提升代码质量和项目稳定性。
37 1
|
2月前
|
Go API 开发者
Golang深入浅出之-文件与目录操作:os与path/filepath包
【4月更文挑战第26天】Go语言标准库`os`和`path/filepath`提供文件读写、目录操作等功能。本文涵盖`os.Open`, `os.Create`, `os.Mkdir`, `filepath.Join`等API的使用,强调了文件关闭、路径处理、并发写入和权限问题的处理,并给出实战代码示例,帮助开发者高效、安全地操作文件与目录。注意使用`defer`关闭文件,`filepath`处理路径分隔符,以及通过同步机制解决并发写入冲突。
49 2
|
2月前
|
安全 Unix Go
Golang深入浅出之-Go语言中的时间与日期处理:time包详解
【4月更文挑战第26天】Go语言的`time`包提供处理日期和时间的功能,包括`time.Time`类型、时间戳、格式化与解析。本文讨论了核心概念、常见问题(如时区处理、格式字符串混淆、超时控制和并发安全)及解决方法。推荐使用`time.LoadLocation`管理时区,熟悉时间格式规则,用`context`精确控制超时,并注意并发安全。文中通过代码示例展示了如何获取格式化时间、计算时间差以及创建定时任务。学习和应用这些知识可提高程序的健壮性和准确性。
71 2
|
2月前
|
JSON 编解码 Go
Golang深入浅出之-HTTP客户端编程:使用net/http包发起请求
【4月更文挑战第25天】Go语言`net/http`包提供HTTP客户端和服务器功能,简化高性能网络应用开发。本文探讨如何发起HTTP请求,常见问题及解决策略。示例展示GET和POST请求的实现。注意响应体关闭、错误处理、内容类型设置、超时管理和并发控制。最佳实践包括重用`http.Client`,使用`context.Context`,处理JSON以及记录错误日志。通过实践这些技巧,提升HTTP编程技能。
40 1
|
2月前
|
数据管理 Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第25天】Go语言中的`context`包在并发、网络请求和长任务中至关重要,提供取消、截止时间和元数据管理。本文探讨`context`基础,如`Background()`、`TODO()`、`WithCancel()`、`WithDeadline()`和`WithTimeout()`。常见问题包括不当传递、过度使用`Background()`和`TODO()`以及忽略错误处理。通过取消和超时示例,强调正确传递上下文、处理取消错误和设置超时以提高应用健壮性和响应性。正确使用`context`是构建稳定高效Go应用的关键。
29 1