Golang中,有没有更好的方法将整数文件读入数组?

19

我需要将一个整数文件读入数组。我已经使用以下代码使其工作:

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语言惯例的方法来加载文件?


4
你漏掉了 fd.Close()。在 readFile 的第二行添加 defer fd.Close() - Mostafa
1
在错误检查之后放置"defer fd.Close()"。当文件读取失败时,由于fd为nil,你将会从这行代码中得到运行时panic。首先检查错误,然后延迟关闭。如果打开失败,则无需关闭。 - burfl
1
澄清一下,这是因为延迟函数会立即评估并稍后执行。因此,当您尝试在空的fd上(它没有方法)延迟fd.Close()时,您将会遇到恐慌。'x := 2; defer fmt.Print(x); x = 3'将打印'2'而不是3。 - burfl
3个回答

25

使用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)
}

5
我会这样做:
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))
}

1
在我看来,“分配容量以避免每次追加时重新调整大小”并没有避免重新调整大小,因为strings.Split中隐藏了一个[]string的调整大小。 - user811773
好的。你知道append在重新分配内存时的行为吗?我猜它可能会分配比现在需要的更大的内存,但不知道有多大。有人知道在源代码中可以找到它吗? - Mostafa
源代码:http://weekly.golang.org/src/pkg/runtime/slice.c 中的函数 runtime·growslice,参数 n 是要追加的元素数量(例如:n=1)。 - user811773
我尝试测试代码,它显示“i已声明但未使用”。 - Tbalz
1
@Tbalz已修复!只是将“i”更改为“_”。 - Xavier Egea
显示剩余3条评论

0

你使用 fmt.Fscanf 的解决方案很好。当然,根据你的情况,还有许多其他方法可以实现。Mostafa 的技术是我经常使用的一种(尽管我可能会使用 make 一次性分配结果。哎呀!划掉。他已经这样做了),但为了最终的控制,你应该学习 bufio.ReadLine。请参见 go readline -> string 获取一些示例代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接