缓冲区的基本概念缓冲区是在内存中分配的一段空间,用于暂时存储数据。当数据从一个设备传送到另一个设备时,缓冲区可以用来平滑这种传输过程,使得数据的发送和接收更加高效。
为什么需要缓冲区背景:计算机磁盘是由一个个磁道构成,每次进行数据读写的时候,都需要通过磁头寻找到到合适的磁道,才能执行读写数据的操作,而这个过程需要耗费一定的时间,即寻道时间。
原理:当我们引入缓冲区后,可以将多次小量的数据写入操作合并为一次大量的写入操作,从而大大减少寻道时间和旋转延迟,提高数据的读写效率。
与缓存的区别:
缓存主要用于存储最近或最经常访问的数据,利用硬件的性能优势,加快数据的访问速度,例如redis缓存利用内存速度远远大于磁盘的优势。在计算机底层则有CPU缓存(L1、L2、L3);内存缓存DRAM Cache;磁盘缓存Disk Cache等,从磁盘到CPU的缓存空间越来越大,性能也越来越好,造价也越来越高
而缓冲区主要用于减少数据传输和磁盘的读写次数,提高数据的读写效率。
缓冲区类型
全缓冲:数据只有在缓冲区满时才会被实际写入或读取。
行缓冲:在文本输入的情况下,数据在遇到换行符时会被实际写入或读取。
无缓冲:每次数据写入或读取都会立即执行。
自行实现带缓冲的I/O用go代码作为演示:
1234567891011121314151617181920212223242526272829303132333435type BufferedFileWriter struct { buffer []byte bufferEndIndex int fout *os.File}func NewWriter(f *os.File, bufferSize int) *BufferedFileWriter { return &BufferedFileWriter{ buffer: make([]byte, bufferSize), bufferEndIndex: 0, fout: f, }}func (w *BufferedFileWriter) Write(p []byte) { if len(p) >= len(w.buffer) { w.flush() _, _ = w.fout.Write(p) } else { if w.bufferEndIndex+len(p) >= len(w.buffer) { w.flush() } copy(w.buffer[w.bufferEndIndex:], p) w.bufferEndIndex += len(p) }}func (w *BufferedFileWriter) flush() { _, _ = w.fout.Write(w.buffer[0:w.bufferEndIndex]) w.bufferEndIndex = 0}func (w *BufferedFileWriter) WriteString(s string) { w.Write([]byte(s))}
与直接写文件和标准库的bufio对比耗时代码:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970//直接写文件func WriteDirectly() { fout, err := os.OpenFile(outFile1, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) if err != nil { HandleError(err) } defer func(fout *os.File) { if err := fout.Close(); err != nil { HandleError(err) } }(fout) for i := 0; i < 9999; i++ { if _, err := fout.WriteString(text + "\n"); err != nil { HandleError(err) } }}//带缓冲区写文件func WriteWithBuffer() { fout, err := os.OpenFile(outFile2, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) if err != nil { HandleError(err) } defer func(fout *os.File) { if err := fout.Close(); err != nil { HandleError(err) } }(fout) // 定义较大的缓冲区减少写磁盘次数,空间换时间 writer := NewWriter(fout, 8*1024) defer writer.flush() for i := 0; i < 9999; i++ { writer.WriteString(text + "\n") }}// bufio写文件func WriteWithBufio() { fout, err := os.Create(outFile3) if err != nil { HandleError(err) } defer func(fout *os.File) { if err := fout.Close(); err != nil { HandleError(err) } }(fout) writer := bufio.NewWriter(fout) defer writer.Flush() for i := 0; i < 9999; i++ { if _, err := writer.WriteString(text + "\n"); err != nil { HandleError(err) } }}func TestBufferIO(t *testing.T) { t1 := time.Now() WriteDirectly() t2 := time.Now() WriteWithBuffer() t3 := time.Now() WriteWithBufio() t4 := time.Now() fmt.Printf("直接写文件耗时:%v, 自行实现带缓冲区写文件:%v, 使用go标准库bufio写文件耗时:%v", t2.Sub(t1), t3.Sub(t2), t4.Sub(t3))}
结果:可以看出,自行实现带缓冲区写文件比直接写文件要快很多,速度接近于使用go标准库的bufio。