在Go中读取CSV文件

Luk*_*don 15 go

我正在学习Go.以下是读取CSV文件的代码段:

func parseLocation(file string) (map[string]Point, error) {
    f, err := os.Open(file)
    defer f.Close()
    if err != nil {
        return nil, err
    }
    lines, err := csv.NewReader(f).ReadAll()
    if err != nil {
        return nil, err
    }
    locations := make(map[string]Point)
    for _, line := range lines {
        name := line[0]
        lat, laterr := strconv.ParseFloat(line[1], 64)
        if laterr != nil {
            return nil, laterr
        }
        lon, lonerr := strconv.ParseFloat(line[2], 64)
        if lonerr != nil {
            return nil, lonerr
        }
        locations[name] = Point{lat, lon}
    }
    return locations, nil
}
Run Code Online (Sandbox Code Playgroud)

有没有办法提高此代码的可读性?如果没有噪音.

Syn*_*les 39

Go 现在为此提供了一个 csv 包。它是encoding/csv。您可以在此处找到文档:https : //golang.org/pkg/encoding/csv/

文档中有几个很好的例子。这是我创建的一个辅助方法,用于读取 csv 文件并返回其记录。

package main

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

func readCsvFile(filePath string) [][]string {
    f, err := os.Open(filePath)
    if err != nil {
        log.Fatal("Unable to read input file " + filePath, err)
    }
    defer f.Close()

    csvReader := csv.NewReader(f)
    records, err := csvReader.ReadAll()
    if err != nil {
        log.Fatal("Unable to parse file as CSV for " + filePath, err)
    }

    return records
}

func main() {
    records := readCsvFile("../tasks.csv")
    fmt.Println(records)
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,它默认使用逗号。如果您想更改此行为,您可以添加 csv.NewReader().Comma=';' 例如,如果分隔符是 ; (2认同)

One*_*One 19

Go是一种非常冗长的语言,但你可以使用这样的东西:

// predeclare err
func parseLocation(file string) (locations map[string]*Point, err error) {
    f, err := os.Open(file)
    if err != nil {
        return nil, err
    }
    defer f.Close() // this needs to be after the err check

    lines, err := csv.NewReader(f).ReadAll()
    if err != nil {
        return nil, err
    }

    //already defined in declaration, no need for :=
    locations = make(map[string]*Point, len(lines))
    var lat, lon float64 //predeclare lat, lon
    for _, line := range lines {
        // shorter, cleaner and since we already have lat and err declared, we can do this.
        if lat, err = strconv.ParseFloat(line[1], 64); err != nil {
            return nil, err
        }
        if lon, err = strconv.ParseFloat(line[2], 64); err != nil {
            return nil, err
        }
        locations[line[0]] = &Point{lat, lon}
    }
    return locations, nil
}
Run Code Online (Sandbox Code Playgroud)

//编辑

@Dustin评论中发布了一个更高效,更合适的版本,为了完整起见,我在这里添加它:

func parseLocation(file string) (map[string]*Point, error) {
    f, err := os.Open(file)
    if err != nil {
        return nil, err
    }
    defer f.Close()

    csvr := csv.NewReader(f)

    locations := map[string]*Point{}
    for {
        row, err := csvr.Read()
        if err != nil {
            if err == io.EOF {
                err = nil
            }
            return locations, err
        }

        p := &Point{}
        if p.lat, err = strconv.ParseFloat(row[1], 64); err != nil {
            return nil, err
        }
        if p.lon, err = strconv.ParseFloat(row[2], 64); err != nil {
            return nil, err
        }
        locations[row[0]] = p
    }
}
Run Code Online (Sandbox Code Playgroud)

playground

  • 我建议不要命名这些返回变量。您甚至都没有使用它们的名字,只会引起额外的混乱。无需在解析之前将整个内容立即读取到内存中。应始终避免这种情况。纬度/经度的预声明范围过大,但实际上是不必要的。我会这样做:http://play.golang.org/p/3YE2pXSF3x (2认同)

the*_*ski 13

我基本上从这里复制了我的答案:https://www.dotnetperls.com/csv-go.对我来说,这是我在stackoverflow上找到的更好的答案.

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

func ReadCsvFile(filePath string)  {
    // Load a csv file.
    f, _ := os.Open(filePath)

    // Create a new reader.
    r := csv.NewReader(f)
    for {
        record, err := r.Read()
        // Stop at EOF.
        if err == io.EOF {
            break
        }

        if err != nil {
            panic(err)
        }
        // Display record.
        // ... Display record length.
        // ... Display all individual elements of the slice.
        fmt.Println(record)
        fmt.Println(len(record))
        for value := range record {
            fmt.Printf("  %v\n", record[value])
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

我也不喜欢默认 Reader 的冗长,所以我创建了一个类似于以下内容的新类型bufio#Scanner

package main
import "encoding/csv"
import "io"

type Scanner struct {
   Reader *csv.Reader
   Head map[string]int
   Row []string
}

func NewScanner(o io.Reader) Scanner {
   csv_o := csv.NewReader(o)
   a, e := csv_o.Read()
   if e != nil {
      return Scanner{}
   }
   m := map[string]int{}
   for n, s := range a {
      m[s] = n
   }
   return Scanner{Reader: csv_o, Head: m}
}

func (o *Scanner) Scan() bool {
   a, e := o.Reader.Read()
   o.Row = a
   return e == nil
}

func (o Scanner) Text(s string) string {
   return o.Row[o.Head[s]]
}
Run Code Online (Sandbox Code Playgroud)

例子:

package main
import "strings"

func main() {
   s := `Month,Day
January,Sunday
February,Monday`

   o := NewScanner(strings.NewReader(s))
   for o.Scan() {
      println(o.Text("Month"), o.Text("Day"))
   }
}
Run Code Online (Sandbox Code Playgroud)

https://golang.org/pkg/encoding/csv