Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go缓冲区

浅析Go语言中的缓冲区及其在fmt包中的应用

作者:沙蒿同学

这篇文章主要为大家详细介绍了Go语言中的缓冲区及其在fmt包中的应用的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下

传统的IO流程

在传统的IO流程中,通常涉及以下几个步骤:

在传统的IO流程中,每次读取或写入操作都会涉及到系统调用,这会导致较高的开销。为了提高性能,通常会使用缓冲区来减少系统调用的次数。缓冲区可以一次读取或写入多个数据,从而减少了系统调用的开销。

缓冲区

上面我们了解到缓冲区这个概念,那什么是缓冲区呢?

内存缓冲区是计算机中的一种临时存储区域,用于暂时存储数据。它通常用于提高数据读写的效率,减少对底层存储设备的频繁访问。

缓冲区的主要目的是在数据的生产者和消费者之间起到一个中间层的作用。当数据被生产者生成时,它首先被写入缓冲区,而不是直接写入到目标存储设备。然后,消费者可以从缓冲区中读取数据。

缓冲区的大小是有限的,一旦缓冲区被填满,生产者必须等待消费者读取数据,以便为新的数据腾出空间。同样,如果缓冲区为空,消费者必须等待生产者生成新的数据。

内存缓冲区可以用于各种场景,比如:

需要注意的是,内存缓冲区只是一个临时存储区域,数据在缓冲区中并不是持久化的。一旦程序结束或缓冲区被清空,缓冲区中的数据就会丢失。因此,在使用内存缓冲区时需要确保数据的正确性和一致性。

go缓冲区

在Go语言中,缓冲区的大小是由创建缓冲区时指定的参数决定的。在标准库中,可以使用bufio包提供的NewWriterSize函数创建一个指定大小的缓冲区。

默认情况下,bufio.Writer的缓冲区大小为4096字节(4KB),即调用bufio.NewWriter创建的缓冲区大小为4096字节。这是因为4096字节是一个常见的磁盘块大小,对于大多数应用场景来说,这个大小已经足够了。

如果需要自定义缓冲区的大小,可以使用bufio.NewWriterSize函数来指定缓冲区的大小。例如,可以通过bufio.NewWriterSize(writer, 8192)来创建一个大小为8192字节(8KB)的缓冲区。

为什么

为什么go编程中要设置缓冲区呢?其实我们上面都有提到:设置缓冲区的一个主要目的就是为了减少频繁的IO操作。

在进行IO操作时,例如读取或写入文件,每次都直接操作底层的存储设备(如磁盘或网络)可能会导致性能下降。这是因为每次IO操作都需要进行系统调用,这涉及到内核和用户空间之间的上下文切换,以及硬件设备的访问延迟。

通过使用缓冲区,可以将数据暂时存储在内存中,而不是直接与底层存储设备进行交互。这样可以将多个小的IO操作合并为一个大的IO操作,从而减少了系统调用的次数。这种批量处理的方式通常比频繁的小IO操作更高效。

此外,缓冲区还可以提供更好的数据传输效率。当数据被写入缓冲区时,实际的IO操作可以被推迟到缓冲区被填满或手动刷新缓冲区时才执行。这样可以减少IO操作的次数,提高数据传输的效率。

go 缓冲区(Buffer)是分配在堆还是栈?

在Go语言中,缓冲区(Buffer)的申请是在堆上进行的。

Go语言中的栈空间是有限的,而且栈上的内存分配和释放是由编译器自动管理的,无法手动控制。因此,较大的缓冲区无法放在栈上进行申请。

相反,Go语言中的堆空间是用于动态分配内存的,可以手动控制内存的申请和释放。当我们使用make关键字创建一个切片或映射时,内存就会在堆上进行动态分配。而bufio包中的缓冲区也是通过make函数在堆上进行申请的。

缓冲区的申请通常是在创建缓冲区时进行的,例如使用bufio.NewWriterbufio.NewWriterSize函数来创建一个缓冲区对象。这个过程会调用make函数来分配足够大小的内存,并返回一个指向该内存的指针。

fmt打印

示例

fmt.Println("Hello, world!"),大家平时用得最多了,这不就是打印输出到控制台嘛

当执行fmt.Println("Hello, world!")命令时,会调用fmt包内的Println函数来打印输出。

首先,Println函数会根据传入的参数列表构建一个字符串,并将其传递给Fprintln函数。Fprintln函数是fmt包内部的一个辅助函数,它会将构建的字符串写入到标准输出(即控制台)。

Fprintln函数内部,它会调用newPrinter函数来创建一个pp(printer)对象。pp对象是printer结构体的实例,它包含了打印输出的相关配置和状态信息。

接下来,Fprintln函数会调用pp.print方法来实际执行打印输出的操作。在print方法中,它会根据配置的格式化选项,将构建的字符串写入到pp.buf缓冲区中。

如果缓冲区已满,或者遇到换行符(\n),print方法会调用pp.write方法将缓冲区的内容写入到标准输出。write方法会使用os.Stdout作为目标,将缓冲区的内容写入到控制台。

最后,Fprintln函数会调用pp.free方法来释放pp对象占用的内存,以及清空缓冲区。

总结起来,当执行fmt.Println("Hello, world!")命令时,fmt包内部会构建打印输出的字符串,并将其写入到标准输出。这个过程涉及到字符串的构建、缓冲区的管理和标准输出的写入。通过使用printer结构体和相关方法,fmt包实现了方便的打印输出功能。

源码查看

// ...
func main() {
    fmt.Println("Hello, world!")
}

// fmt 包
// ...
func Println(a ...any) (n int, err error) {
	return Fprintln(os.Stdout, a...)
}
// ...
func Fprintln(w io.Writer, a ...any) (n int, err error) {
	p := newPrinter()
	p.doPrintln(a)
	n, err = w.Write(p.buf)
	p.free()
	return
}

当打印内容很大怎么办

当打印的内容超出了缓冲区的大小时,fmt包会动态扩展缓冲区的大小,以容纳更大的数据,并完整地输出到标准输出。

在执行打印操作时,fmt包会检查缓冲区的剩余空间是否足够容纳当前要打印的内容。如果空间不足,fmt包会自动扩展缓冲区的大小,通常是将缓冲区的大小翻倍。

例如,在默认情况下,fmt包的缓冲区大小为4KB。如果要打印的内容超过了4KB,fmt包会自动将缓冲区扩展到8KB,以容纳更多的数据。如果仍然不够,会继续扩展到16KB,以此类推,直到能够容纳所有的数据。

当缓冲区大小足够容纳要打印的内容时,fmt包会将数据写入缓冲区中。当缓冲区满了或者遇到换行符(\n)时,fmt包会将缓冲区的内容写入到标准输出,确保完整地输出所有的数据。

也就是说我有一个8k的打印内容,而缓冲区大小为4KB,那么在第一次写入缓冲区后,缓冲区将被填满。此时,fmt包会进行一次IO操作,将缓冲区的内容写入标准输出。

由于缓冲区已满,第二次写入操作将触发另一次IO操作,将剩余的内容写入标准输出。

因此,在这种情况下,fmt包会进行两次IO操作,将完整的8KB内容写入标准输出。第一次是在缓冲区填满后,第二次是在第二次写入时。

到此这篇关于浅析Go语言中的缓冲区及其在fmt包中的应用的文章就介绍到这了,更多相关Go缓冲区内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文