Golang中File与bufio.NewReader处理CSV文件的对比

Golang中File与bufio.NewReader处理CSV文件的对比 我正在学习Go语言的入门阶段。

我尝试解决这个网站上的问题:https://gophercises.com/exercises/quiz,虽然我已经成功解决了,但还有几个疑问。

  1. 读取CSV文件的最佳实践(惯用方式)是什么?以下两种方式有什么区别?
csvFile, _ := os.Open("problems.csv")
reader := csv.NewReader(bufio.NewReader(csvFile))

或者

csvFile, _ := os.Open("problems.csv")
reader := csv.NewReader(csvFile)

更多关于Golang中File与bufio.NewReader处理CSV文件的对比的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

什么是惯用的方法?

更多关于Golang中File与bufio.NewReader处理CSV文件的对比的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这取决于你是否真的需要缓冲读取器。

csv.NewReader 期望接收一个"普通"的 io.Reader,因此并不要求你创建缓冲读取器。

缓冲读取器会带来一些内存开销,除非确实需要,否则我会尽量避免使用。

csv.NewReader

使用 bufio.NewReader 会在底层调用 bufio.NewReaderSize,其文档说明如下:

// NewReaderSize returns a new Reader whose buffer has at least the specified
// size. If the argument io.Reader is already a Reader with large enough
// size, it returns the underlying Reader.

因此,是否存在差异取决于 os.Open 返回的 io.Reader。如果其缓冲区"足够大",则没有区别。默认缓冲区大小为 4096

在Go语言中处理CSV文件时,两种方式的主要区别在于缓冲机制的使用。

第一种方式使用了bufio.NewReader创建了一个带缓冲的读取器:

csvFile, _ := os.Open("problems.csv")
reader := csv.NewReader(bufio.NewReader(csvFile))

这种方式会在底层创建一个缓冲区(默认大小为4096字节),减少对文件系统的直接读取次数,对于较大的CSV文件或需要频繁读取小数据块的场景性能更好。

第二种方式直接使用文件对象:

csvFile, _ := os.Open("problems.csv")
reader := csv.NewReader(csvFile)

这种方式每次读取都会直接访问文件系统,对于小文件或读取次数较少的情况足够使用。

性能对比示例:

package main

import (
	"bufio"
	"encoding/csv"
	"fmt"
	"os"
	"time"
)

func main() {
	// 测试带缓冲的读取
	start := time.Now()
	csvFile1, _ := os.Open("problems.csv")
	reader1 := csv.NewReader(bufio.NewReader(csvFile1))
	records1, _ := reader1.ReadAll()
	csvFile1.Close()
	fmt.Printf("带缓冲读取耗时: %v\n", time.Since(start))

	// 测试直接读取
	start = time.Now()
	csvFile2, _ := os.Open("problems.csv")
	reader2 := csv.NewReader(csvFile2)
	records2, _ := reader2.ReadAll()
	csvFile2.Close()
	fmt.Printf("直接读取耗时: %v\n", time.Since(start))

	fmt.Printf("记录数: %d vs %d\n", len(records1), len(records2))
}

在实际应用中,对于大多数CSV文件处理场景,推荐使用带缓冲的方式,因为它能提供更好的性能,特别是在处理大型文件时。CSV解析器本身也需要多次小量读取来解析字段和行,缓冲机制能显著减少系统调用次数。

两种方式在API使用上完全一致,后续的读取操作没有任何区别:

records, err := reader.ReadAll()
// 或者逐行读取
for {
    record, err := reader.Read()
    if err != nil {
        break
    }
    // 处理记录
}
回到顶部