英文:
Golang, is there a better way read a file of integers into an array?
问题
我需要将一个包含整数的文件读入到一个数组中。我已经用以下代码实现了这个功能:
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 语言习惯的方法来加载一个文件?
英文:
I need to read a file of integers into an array. I have it working with this:
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))
}
The file numbers.txt is just:
1
2
3
...
ReadFile() seems too long (maybe because of the error handing).
Is there a shorter / more Go idiomatic way to load a file?
答案1
得分: 25
使用bufio.Scanner很方便。我还使用了io.Reader而不是使用文件名。通常这是一个很好的技巧,因为它允许代码在_任何_类似文件的对象上使用,而不仅仅是磁盘上的文件。这里它从一个字符串中“读取”。
package main
import (
	"bufio"
	"fmt"
	"io"
	"strconv"
	"strings"
)
// ReadInts从r中读取以空格分隔的整数。如果有错误,它会返回到目前为止成功读取的整数以及错误值。
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)
}
英文:
Using a bufio.Scanner makes things nice. I've also used an io.Reader rather than taking a filename. Often that's a good technique, since it allows the code to be used on any file-like object and not just a file on disk. Here it's "reading" from a string.
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
得分: 5
我会这样做:
package main
import (
"fmt"
    "io/ioutil"
    "strconv"
    "strings"
)
// 最好让这样的函数返回错误,而不是自己处理错误。
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")
    // 为了避免每次追加都重新分配内存,给切片分配容量。
    nums = make([]int, 0, len(lines))
    for _, l := range lines {
        // 当使用Split时,文件末尾会出现空行。
        if len(l) == 0 { continue }
        // 当我们确切知道要处理的内容时,Atoi更适合这个任务。Scanf是更通用的选项。
        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))
}
英文:
I would do it like this:
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))
}
答案3
得分: 0
你使用 fmt.Fscanf 的解决方案是可以的。根据你的情况,当然还有其他几种方法可以实现。Mostafa 的技巧是我经常使用的一种(尽管我可能会使用 make 一次性分配结果。哎呀!划掉。他确实这样做了。),但是为了最大的控制力,你应该学习 bufio.ReadLine。请参考 https://stackoverflow.com/questions/6141604/go-readline-string 上的一些示例代码。
英文:
Your solution with fmt.Fscanf is fine. There are certainly a number of other ways to do though, depending on your situation. Mostafa's technique is one I use a lot (although I might allocate the result all at once with make. oops! scratch that. He did.) but for ultimate control you should learn bufio.ReadLine. See https://stackoverflow.com/questions/6141604/go-readline-string for some example code.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论