在Go中高效读写CSV

在Go中高效读写CSV,csv,go,Csv,Go,下面的Go代码读取10000条CSV记录(时间戳时间和浮点值),对数据运行一些操作,然后将原始值连同分数的附加列写入另一个CSV。但是它非常慢(即数小时,但大部分时间都是calculateStuff()),我很好奇CSV读/写是否有效率低下的问题 package main import ( "encoding/csv" "log" "os" "strconv" ) func ReadCSV(filepath string) ([][]string, error) { cs

下面的Go代码读取10000条CSV记录(时间戳
时间
和浮点
),对数据运行一些操作,然后将原始值连同
分数
的附加列写入另一个CSV。但是它非常慢(即数小时,但大部分时间都是
calculateStuff()
),我很好奇CSV读/写是否有效率低下的问题

package main

import (
  "encoding/csv"
  "log"
  "os"
  "strconv"
)

func ReadCSV(filepath string) ([][]string, error) {
  csvfile, err := os.Open(filepath)

  if err != nil {
    return nil, err
  }
  defer csvfile.Close()

  reader := csv.NewReader(csvfile)
  fields, err := reader.ReadAll()

  return fields, nil
}

func main() {
  // load data csv
  records, err := ReadCSV("./path/to/datafile.csv")
  if err != nil {
    log.Fatal(err)
  }

  // write results to a new csv
  outfile, err := os.Create("./where/to/write/resultsfile.csv"))
  if err != nil {
    log.Fatal("Unable to open output")
  }
  defer outfile.Close()
  writer := csv.NewWriter(outfile)

  for i, record := range records {
    time := record[0]
    value := record[1]

    // skip header row
    if i == 0 {
      writer.Write([]string{time, value, "score"})
      continue
    }

    // get float values
    floatValue, err := strconv.ParseFloat(value, 64)
    if err != nil {
      log.Fatal("Record: %v, Error: %v", floatValue, err)
    }

    // calculate scores; THIS EXTERNAL METHOD CANNOT BE CHANGED
    score := calculateStuff(floatValue)

    valueString := strconv.FormatFloat(floatValue, 'f', 8, 64)
    scoreString := strconv.FormatFloat(prob, 'f', 8, 64)
    //fmt.Printf("Result: %v\n", []string{time, valueString, scoreString})

    writer.Write([]string{time, valueString, scoreString})
  }

  writer.Flush()
}

我正在寻找帮助,使这个CSV读/写模板代码尽可能快。对于这个问题的范围,我们不必担心
calculateStuff
方法。

编码/csv
在大文件上确实非常慢,因为它执行大量分配。由于格式非常简单,我建议使用
strings.Split
,这会更快

如果这不够快,您可以考虑使用<代码>字符串来实现解析。


话虽如此,如果文件比内存大,您也应该重新考虑使用
ReadAll

您首先将文件加载到内存中,然后再处理它,这对于大文件来说可能会很慢

您需要循环并调用
。读取
并一次处理一行

func processCSV(rc io.Reader) (ch chan []string) {
    ch = make(chan []string, 10)
    go func() {
        r := csv.NewReader(rc)
        if _, err := r.Read(); err != nil { //read header
            log.Fatal(err)
        }
        defer close(ch)
        for {
            rec, err := r.Read()
            if err != nil {
                if err == io.EOF {
                    break
                }
                log.Fatal(err)

            }
            ch <- rec
        }
    }()
    return
}
func processCSV(rc io.Reader)(ch chan[]字符串){
ch=制造(chan[]字符串,10)
go func(){
r:=csv.NewReader(rc)
如果{,err:=r.Read();err!=nil{//Read头
log.Fatal(错误)
}
延迟关闭(ch)
为了{
rec,err:=r.Read()
如果错误!=零{
如果err==io.EOF{
打破
}
log.Fatal(错误)
}
ch这基本上是作者在评论部分的回答:

package main

import (
  "encoding/csv"
  "log"
  "os"
  "strconv"
)

func main() {
  // setup reader
  csvIn, err := os.Open("./path/to/datafile.csv")
  if err != nil {
    log.Fatal(err)
  }
  r := csv.NewReader(csvIn)

  // setup writer
  csvOut, err := os.Create("./where/to/write/resultsfile.csv"))
  if err != nil {
    log.Fatal("Unable to open output")
  }
  w := csv.NewWriter(csvOut)
  defer csvOut.Close()

  // handle header
  rec, err := r.Read()
  if err != nil {
    log.Fatal(err)
  }
  rec = append(rec, "score")
  if err = w.Write(rec); err != nil {
    log.Fatal(err)
  }

  for {
    rec, err = r.Read()
    if err != nil {
      if err == io.EOF {
        break
      }
      log.Fatal(err)
    }

    // get float value
    value := rec[1]
    floatValue, err := strconv.ParseFloat(value, 64)
    if err != nil {
      log.Fatal("Record, error: %v, %v", value, err)
    }

    // calculate scores; THIS EXTERNAL METHOD CANNOT BE CHANGED
    score := calculateStuff(floatValue)

    scoreString := strconv.FormatFloat(score, 'f', 8, 64)
    rec = append(rec, scoreString)

    if err = w.Write(rec); err != nil {
      log.Fatal(err)
    }
  w.Flush()
  }
}

注意:当然,逻辑都被阻塞在
main()中
,最好将其分成几个函数,但这超出了问题的范围。

速度“非常慢”有多慢?您正在测试的csv文件的文件大小是多少?因为“慢”一词是主观的……假设计算仅依赖于当前记录(例如,您不需要合计一列的所有值或排序等)不要将整个文件吸入内存,而是在读取时对每个记录进行操作,然后将其写出。当您可以对流进行操作时,请避免使用
ioutil.ReadAll
之类的操作。在任何情况下,除非您正在交换(由于使用太多内存)你可能是IO绑定的。另外,请确保检查错误!!例如,如果这是整个程序(即,如果它不是较大程序的一部分),则更明智的做法是不要弄乱文件名,而只是从
os.Stdin
读取并写入
os.Stdout
,并让shell处理打开的文件(或使用其他程序的直接输出/输入!)。使用
bufio.Scanner
bytes.LastIndex
的更快实现可能看起来像。它比
编码/csv
快约3倍,但这牺牲了
编码/csv
提供的大量错误检查和灵活性。