【问题标题】:Golang, is there a better way read a file of integers into an array?Golang,有没有更好的方法将整数文件读入数组?
【发布时间】:2012-03-25 17:41:54
【问题描述】:

我需要将一个整数文件读入一个数组。我有它的工作:

package main

import (
    "fmt"
    "io"
    "os"
)

func readFile(filePath string) (numbers []int) {
    fd, err := os.Open(filePath)
    if err != nil {
        panic(fmt.Sprintf("open %s: %v", filePath, err))
    }
    var line int
    for {

        _, err := fmt.Fscanf(fd, "%d\n", &line)

        if err != nil {
            fmt.Println(err)
            if err == io.EOF {
                return
            }
            panic(fmt.Sprintf("Scan Failed %s: %v", filePath, err))

        }
        numbers = append(numbers, line)
    }
    return
}

func main() {
    numbers := readFile("numbers.txt")
    fmt.Println(len(numbers))
}

文件 numbers.txt 只是:

1
2
3
...

ReadFile() 似乎太长(可能是因为处理错误)。

是否有更短/更符合 Go 语言习惯的方式来加载文件?

【问题讨论】:

  • 你错过了fd.Close()。添加defer fd.Close() 作为readFile 的第二行。
  • 在错误检查之后放置'defer fd.Close()'。当文件读取失败时,您将从该行得到运行时恐慌,因为 fd 为 nil。首先检查错误,然后推迟关闭。如果您打开失败,您无论如何都不需要关闭。
  • 澄清一下,这是因为延迟会立即评估并稍后执行。因此,当您尝试在 nil fd(没有方法)上延迟 fd.Close() 时,您会感到恐慌。 'x := 2;推迟 fmt.Print(x); x = 3' 将打印 '2',而不是 3。

标签: go


【解决方案1】:

使用bufio.Scanner 会让事情变得更好。我还使用了io.Reader,而不是使用文件名。这通常是一种很好的技术,因为它允许在任何类文件对象上使用代码,而不仅仅是磁盘上的文件。这里是从字符串中“读取”。

package main

import (
    "bufio"
    "fmt"
    "io"
    "strconv"
    "strings"
)

// ReadInts reads whitespace-separated ints from r. If there's an error, it
// returns the ints successfully read so far as well as the error value.
func ReadInts(r io.Reader) ([]int, error) {
    scanner := bufio.NewScanner(r)
    scanner.Split(bufio.ScanWords)
    var result []int
    for scanner.Scan() {
        x, err := strconv.Atoi(scanner.Text())
        if err != nil {
            return result, err
        }
        result = append(result, x)
    }
    return result, scanner.Err()
}

func main() {
    tf := "1\n2\n3\n4\n5\n6"
    ints, err := ReadInts(strings.NewReader(tf))
    fmt.Println(ints, err)
}

【讨论】:

    【解决方案2】:

    我会这样做:

    package main
    
    import (
    "fmt"
        "io/ioutil"
        "strconv"
        "strings"
    )
    
    // It would be better for such a function to return error, instead of handling
    // it on their own.
    func readFile(fname string) (nums []int, err error) {
        b, err := ioutil.ReadFile(fname)
        if err != nil { return nil, err }
    
        lines := strings.Split(string(b), "\n")
        // Assign cap to avoid resize on every append.
        nums = make([]int, 0, len(lines))
    
        for _, l := range lines {
            // Empty line occurs at the end of the file when we use Split.
            if len(l) == 0 { continue }
            // Atoi better suits the job when we know exactly what we're dealing
            // with. Scanf is the more general option.
            n, err := strconv.Atoi(l)
            if err != nil { return nil, err }
            nums = append(nums, n)
        }
    
        return nums, nil
    }
    
    func main() {
        nums, err := readFile("numbers.txt")
        if err != nil { panic(err) }
        fmt.Println(len(nums))
    }
    

    【讨论】:

    • 在我看来,“分配上限以避免在每次追加时调整大小”并不能避免调整大小,因为 []string 的调整大小隐藏在 strings.Split 中的某处。
    • 否,strings.Split 首先找到sep 的出现次数,并使用该次数进行分配。见genSplit
    • 确实如此,但strings.Split 这样做的代价是要遍历字符串两次——我没想到会这样。无论如何,append 在每次追加时都调整大小是不正确的。
    • 对。你知道append 的重新分配行为吗?我也猜想它可能分配的比现在需要的要大,但不知道要大多少。有谁知道我们在哪里可以找到它的源代码?
    • @Tbalz 已修复!刚刚把“i”改成了“_”。
    【解决方案3】:

    您使用 fmt.Fscanf 的解决方案很好。当然,根据您的情况,还有许多其他方法可以做。 Mostafa 的技术是我经常使用的技术(尽管我可能会使用 make 一次性分配所有结果。哎呀!从头开始。他做到了。)但是为了最终控制,你应该学习 bufio.ReadLine。示例代码见go readline -> string

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-02-04
      • 1970-01-01
      • 1970-01-01
      • 2013-11-06
      • 2022-01-23
      • 2021-11-27
      • 2011-06-12
      相关资源
      最近更新 更多