深入解析golang bufio

 更新时间:2022年04月21日 10:34:33   作者:charlieroro  
这篇文章主要介绍了golang bufio解析,golang的bufio库使用缓存来一次性进行大块数据的读写,以此降低IO系统调用,提升性能,需要的朋友可以参考下

bufio 包介绍 

bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。

golang bufio

当频繁地对少量数据读写时会占用IO,造成性能问题。golang的bufio库使用缓存来一次性进行大块数据的读写,以此降低IO系统调用,提升性能。

在Transport中可以设置一个名为WriteBufferSize的参数,该参数指定了底层(Transport.dialConn)写buffer的大小。

	tr := &http.Transport{
		WriteBufferSize:     64 * 1024,
	}
	pconn.br = bufio.NewReaderSize(pconn, t.readBufferSize())
	pconn.bw = bufio.NewWriterSize(persistConnWriter{pconn}, t.writeBufferSize())

使用bufio进行写

可以使用bufio.NewWriter初始化一个大小为4096字节的Writer(见下),或使用bufio.NewWriterSize初始化一个指定大小的Writer

Writer中的主要参数为缓存区buf,缓存区中的数据偏移量n以及写入接口wr

type Writer struct {
	err error
	buf []byte
	n   int
	wr  io.Writer
}

bufio.Writer方法可以一次性写入缓存中的数据,通常有如下三种情况:

  • 缓存中满数据
  • 缓存中仍有空间
  • 待写入的数据大于缓存的大小

缓存中满数据

当缓存中满数据时,会执行写操作。

缓存中仍有空间

如果缓存中仍有数据,则不会执行写入动作,除非调用Flush()方法。

待写入的数据大于缓存的大小

由于此时缓存无法缓存足够的数据,此时会跳过缓存直接执行写操作

type Writer int
func (*Writer) Write(p []byte) (n int, err error) {
	fmt.Printf("Writing: %s\n", p)
	return len(p), nil
}
func main() {
	w := new(Writer)
	bw1 := bufio.NewWriterSize(w, 4)
	// Case 1: Writing to buffer until full
	bw1.Write([]byte{'1'})
	bw1.Write([]byte{'2'})
	bw1.Write([]byte{'3'})
	bw1.Write([]byte{'4'}) // write - buffer is full
	// Case 2: Buffer has space
    bw1.Write([]byte{'5'}) //此时buffer中无法容纳更多的数据,执行写操作,写入 []byte{'1','2','3','4'}
	err = bw1.Flush() // forcefully write remaining
	if err != nil {
		panic(err)
	}
	// Case 3: (too) large write for buffer
	// Will skip buffer and write directly
	bw1.Write([]byte("12345")) //buffer不足,直接执行写操作
//结果:
Writing: 1234
Writing: 5
Writing: 12345

缓存重用

申请缓存对性能是有损耗的,可以使用Reset方法重置缓存,其内部只是将Writer的数据偏移量n置0。

wr := new(Writer)
bw := bufio.NewWriterSize(wr,2) 
bw.Reset(wr) 

获取缓存的可用空间数

Available()方法可以返回缓存的可用空间数,即len(Writer.buf)-Writer.n

使用bufio进行读

与用于写数据的Writer类似,读数据也有一个Reader,可以使用NewReader初始化一个大小为4096字节的Reader,或使用NewReaderSize初始化一个指定大小的Reader(要求最小为16字节)。Reader也有一个记录偏移量的变量r

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 // last byte read for UnreadByte; -1 means invalid
	lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
}

Peek

该方法会返回buf中的前n个字节的内容,但与Read操作不同的是,它不会消费缓存中的数据,即不会增加数据偏移量,因此通常也会用于判断是否读取结束(EOF)。通常有如下几种情况:

  • 如果peak的值小于缓存大小,则返回相应的内容
  • 如果peak的值大于缓存大小,则返回bufio.ErrBufferFull错误
  • 如果peak的值包含EOF且小于缓存大小,则返回EOF

Read

将数据读取到p,涉及将数据从缓存拷贝到p

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

ReadSlice

该方法会读从缓存读取数据,直到遇到第一个delim。如果缓存中没有delim,则返回EOF,如果查询的长度超过了缓存大小,则返回 io.ErrBufferFull 错误。

func (b *Reader) ReadSlice(delim byte) (line []byte, err error) 

例如delim',',则下面会返回的内容为1234,

r := strings.NewReader("1234,567")
rb := bufio.NewReaderSize(r, 20)
fmt.Println(rb.ReadSlice(','))
// 结果:[49 50 51 52 44] <nil>

注意:ReadSlice返回的是原始缓存中的内容,如果针对缓存作并发操作,则返回的内容有可能被其他操作覆盖。因此在官方注释里面有写,建议使用ReadBytesReadString。但ReadBytesReadString涉及内存申请和拷贝,因此会影响性能。在追求高性能的场景下,建议外部使用sync.pool来提供缓存。

// Because the data returned from ReadSlice will be overwritten
// by the next I/O operation, most clients should use
// ReadBytes or ReadString instead.

ReadLine

ReadLine() (line []byte, isPrefix bool, err error)

ReadLine底层用到了ReadSlice,但在返回时会移除\n 或\r\n。需要注意的是,如果切片中没有找到换行符,则不会返回EOF或io.ErrBufferFull 错误,相反,它会将isPrefix置为true

ReadBytes

ReadSlice类似,但它会返回一个新的切片,因此便于并发使用。如果找不到delimReadBytes会返回io.EOF

func (b *Reader) ReadBytes(delim byte) ([]byte, error)

Scanner

scanner可以不断将数据读取到缓存(默认64*1024字节)。

    rb := strings.NewReader("12345678901234567890")
	scanner := bufio.NewScanner(rb)
	for scanner.Scan() {
		fmt.Printf("Token (Scanner): %q\n", scanner.Text())
	}
	// 结果:Token (Scanner): "12345678901234567890"

参考

how-to-read-and-write-with-golang-bufio

到此这篇关于golang bufio解析的文章就介绍到这了,更多相关golang bufio内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang调用shell命令(实时输出,终止)

    golang调用shell命令(实时输出,终止)

    本文主要介绍了golang调用shell命令(实时输出,终止),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • 详解如何使用Go的Viper来解析配置信息

    详解如何使用Go的Viper来解析配置信息

    Viper库为Golang语言开发者提供了对不同数据源和不同格式的配置文件的读取,是Go项目读取配置的神器,我们今天就来讲讲如何使用Viper来解析配置信息,文中通过代码示例讲解非常详细,需要的朋友可以参考下
    2024-01-01
  • go语言里包的用法实例

    go语言里包的用法实例

    这篇文章主要介绍了go语言里包的用法,实例分析了Go语言里包的原理与使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • Goland项目使用gomod配置的详细步骤

    Goland项目使用gomod配置的详细步骤

    Goland是一个用于Go语言开发的IDE,Goland的项目结构与Go语言的项目结构相似,下面这篇文章主要给大家介绍了关于Goland项目使用gomod配置的详细步骤,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • Golang中使用errors返回调用堆栈信息

    Golang中使用errors返回调用堆栈信息

    这篇文章给大家介绍了Golang中如何使用errors返回调用堆栈信息,文章通过代码示例给大家介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2023-12-12
  • Golang中Append()使用实例详解

    Golang中Append()使用实例详解

    今天在刷leetcode的时候,第113题让我遇到了一个Go语言中append函数的一个坑,所以复习下,这篇文章主要给大家介绍了关于Golang中Append()使用的相关资料,需要的朋友可以参考下
    2023-01-01
  • Go 语言 JSON 标准库的使用

    Go 语言 JSON 标准库的使用

    今天通过本文给大家介绍Go 语言 JSON 标准库的使用小结,包括序列化和反序列化的相关知识,感兴趣的朋友跟随小编一起看看吧
    2021-10-10
  • go语言实现markdown解析库的方法示例

    go语言实现markdown解析库的方法示例

    这篇文章主要介绍了go语言实现markdown解析库的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • golang生成vcf通讯录格式文件详情

    golang生成vcf通讯录格式文件详情

    这篇文章主要介绍了golang生成vcf通讯录格式文件详情,​VCF是通讯录格式文件,一般需要用手机通讯录导入导出的文件格式都是vcf格式。​下面详细内容介绍需要的小伙伴可以参考一下
    2022-03-03
  • Golang生成Excel文档的方法步骤

    Golang生成Excel文档的方法步骤

    生成Excel是一个很常见的需求,本文将介绍如何使用Go的 Excelize库去生成Excel文档,以及一些具体场景下的代码实现,感兴趣的可以参考一下
    2021-06-06

最新评论