缓冲区的基本概念
缓冲区是在内存中分配的一段空间,用于暂时存储数据。当数据从一个设备传送到另一个设备时,缓冲区可以用来平滑这种传输过程,使得数据的发送和接收更加高效。
为什么需要缓冲区
背景:
计算机磁盘是由一个个磁道构成,每次进行数据读写的时候,都需要通过磁头寻找到到合适的磁道,才能执行读写数据的操作,而这个过程需要耗费一定的时间,即寻道时间。
原理:
当我们引入缓冲区后,可以将多次小量的数据写入操作合并为一次大量的写入操作,从而大大减少寻道时间和旋转延迟,提高数据的读写效率。
与缓存的区别:
缓存主要用于存储最近或最经常访问的数据,利用硬件的性能优势,加快数据的访问速度,例如redis缓存利用内存速度远远大于磁盘的优势。在计算机底层则有CPU缓存(L1、L2、L3);内存缓存DRAM Cache;磁盘缓存Disk Cache等,从磁盘到CPU的缓存空间越来越大,性能也越来越好,造价也越来越高
而缓冲区主要用于减少数据传输和磁盘的读写次数,提高数据的读写效率。
缓冲区类型
- 全缓冲:数据只有在缓冲区满时才会被实际写入或读取。
- 行缓冲:在文本输入的情况下,数据在遇到换行符时会被实际写入或读取。
- 无缓冲:每次数据写入或读取都会立即执行。
自行实现带缓冲的I/O
用go代码作为演示:
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 29 30 31 32 33 34 35
| type 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对比耗时
代码:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| 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") } }
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。