在Golang中如何统计CSV文件每行的字节数?
如何准确统计CSV文件每行的原始字节数(包含逗号和换行符)
我有一个test.csv文件,需要逐行读取并统计每行的字节数。在Windows系统下,前两行带有\r\n换行符,文件总字节数应为37。使用csv.NewReader()统计时,发现该Reader仅统计了字段内容的字节数,未计入每行的逗号分隔符和换行符。目前想到的手动计算逗号数量再加换行符字节数的方法比较繁琐,想找更优方案。
现有代码如下:
package main import ( "encoding/csv" "fmt" "io" "log" "os" ) func main() { file, err := os.Open("test.csv") if err != nil { log.Fatal(err) } defer file.Close() fileInfo, err := file.Stat() if err != nil { log.Fatal(err) } fmt.Printf("file total bytes is %d\n", fileInfo.Size()) // init reader reader := csv.NewReader(file) // extract the header headers, err := reader.Read() if err != nil { log.Fatal(err) } fmt.Printf("headers are: %+v\n", headers) byteCounter := 0 for { // if we reached end of file, stop if err == io.EOF { break } // read a record record, err := reader.Read() if err != nil { log.Fatal(err) } // loop through each record and count how many bytes for _, item := range record { byteCounter += len(item) fmt.Printf("record is %d bytes\n", len(item)) } fmt.Println("total bytes so far is: ", byteCounter) } }
问题分析
csv.NewReader的核心作用是解析CSV的字段内容,会自动过滤掉逗号分隔符、换行符等格式字符,所以用它统计的字节数必然缺失这些部分——这是设计特性而非问题。要统计每行的原始字节数,需要绕开CSV解析器的字段提取逻辑,或者结合文件偏移量来计算。
方案一:直接读取原始行字节(推荐,简单高效)
使用bufio.Reader的ReadBytes('\n')方法直接读取每行的原始字节流,该方法会返回包含换行符(Windows下为\r\n)的整行数据,直接统计返回字节切片的长度即可得到该行的实际字节数,包括逗号和换行符。
代码示例:
package main import ( "bufio" "fmt" "log" "os" ) func main() { file, err := os.Open("test.csv") if err != nil { log.Fatal(err) } defer file.Close() fileInfo, err := file.Stat() if err != nil { log.Fatal(err) } fmt.Printf("file total bytes is %d\n", fileInfo.Size()) reader := bufio.NewReader(file) totalBytes := 0 lineNum := 0 for { // 读取包含换行符的整行字节 line, err := reader.ReadBytes('\n') if err != nil { if err.Error() != "EOF" { log.Fatal(err) } // 处理最后一行无换行符的情况 if len(line) > 0 { lineNum++ lineBytes := len(line) totalBytes += lineBytes fmt.Printf("第%d行:%d字节\n", lineNum, lineBytes) } break } lineNum++ lineBytes := len(line) totalBytes += lineBytes fmt.Printf("第%d行:%d字节\n", lineNum, lineBytes) } fmt.Printf("统计总字节数:%d\n", totalBytes) }
方案二:解析CSV字段同时统计原始行字节
如果需要同时解析CSV字段内容,又要统计每行的原始字节数,可以通过记录文件的读写偏移量来计算。每次调用csv.Read()前后获取文件当前位置,两者的差值就是该行原始数据的字节数。
代码示例:
package main import ( "encoding/csv" "fmt" "io" "log" "os" ) func main() { file, err := os.Open("test.csv") if err != nil { log.Fatal(err) } defer file.Close() fileInfo, err := file.Stat() if err != nil { log.Fatal(err) } fmt.Printf("file total bytes is %d\n", fileInfo.Size()) reader := csv.NewReader(file) // 禁用注释解析,避免误判行内容 reader.Comment = 0 totalBytes := 0 lineNum := 0 for { // 记录读取前的文件位置 posBefore, err := file.Seek(0, io.SeekCurrent) if err != nil { log.Fatal(err) } record, err := reader.Read() if err != nil { if err == io.EOF { // 处理最后一行的剩余字节 posEnd, _ := file.Seek(0, io.SeekCurrent) if posEnd > posBefore { lineNum++ lineBytes := posEnd - posBefore totalBytes += int(lineBytes) fmt.Printf("第%d行:%d字节,字段:%+v\n", lineNum, lineBytes, record) } break } log.Fatal(err) } // 记录读取后的文件位置 posAfter, err := file.Seek(0, io.SeekCurrent) if err != nil { log.Fatal(err) } lineNum++ lineBytes := posAfter - posBefore totalBytes += int(lineBytes) fmt.Printf("第%d行:%d字节,字段:%+v\n", lineNum, lineBytes, record) } fmt.Printf("统计总字节数:%d\n", totalBytes) }
总结
- 若仅需统计每行字节数,方案一最直接高效,无需处理CSV解析逻辑;
- 若需同时解析CSV字段和统计字节,方案二通过文件偏移量计算,能准确获取每行原始字节数,且保留CSV解析能力。
内容的提问来源于stack exchange,提问作者Coldchain9
相关产品推荐
相关产品推荐

