文件读取是在任何编程语言中最常见操作之一。在本教程中,我们将了解如何使用 Go 读取文件。

本教程包含以下部分。

  • 将整个文件读入内存

    • 使用绝对文件路径

    • 将文件路径作为命令行标志传递

    • 将文件捆绑在二进制文件中

  • 一小块一小块读取文件

  • 一行一行读取文件

将整个文件读入内存

最基本的文件操作之一是将整个文件读入内存。这是在 ioutil 包的 ReadFile 函数的帮助下完成的。

让我们读取一个文件并打印其内容。

我通过运行 mkdir ~/Documents/filehandling 在我的 Documents 目录下创建了一个文件夹 filehandling。

在 filehandling 目录下运行以下命令,创建一个名为 filehandling 的 Go 模块。

  1. go mod init filehandling

我有一个文本文件 test.txt,它将从我们的 Go 程序 filehandling.go 中读取。test.txt 包含以下字符串

  1. Hello World. Welcome to file handling in Go.

这是我的目录结构。


  1. ├── Documents
  2. └── filehandling
  3. ├── filehandling.go
  4. | ├── go.mod
  5. └── test.txt

我们马上进入代码。创建一个文件 filehandling.go,内容如下。

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. )
  6. func main() {
  7. data, err := ioutil.ReadFile("test.txt")
  8. if err != nil {
  9. fmt.Println("File reading error", err)
  10. return
  11. }
  12. fmt.Println("Contents of file:", string(data))
  13. }

请从本地环境运行此程序,因为无法在 playground 上读取文件。

上面的程序第九行中读取文件并返回一个存储在 data 中的字节切片。我们将 data 转换为 string 并显示文件的内容。

请从 test.txt 所在的位置运行该程序。

例如,在 linux/mac 的情况下,如果 test.txt 位于 home/naveen/go/src/filehandling,则使用以下步骤运行该程序,


  1. cd ~/Documents/filehandling/
  2. go install
  3. filehandling

该程序将输出,


  1. Contents of file: Hello World. Welcome to file handling in Go.

如果此程序从任何其他位置运行,例如尝试从 /home/userdirectory 运行程序,它将打印以下错误。


  1. File reading error open test.txt: The system cannot find the file specified.

原因是Go是一种编译语言。go install 的作用是,它从源代码创建二进制文件。二进制文件独立于源代码,可以从任何位置运行。由于在运行二进制文件的位置找不到test.txt,程序会报错它无法找到指定的文件。

有三种方法可以解决这个问题,

  1. 使用绝对文件路径


  1. 将文件路径作为命令行标志传递


  1. 将文本文件与二进制文件捆绑在一起

我们一个一个地讨论它们。

1.使用绝对文件路径

解决此问题的最简单方法是传递绝对文件路径。我修改了程序并将路径更改为绝对路径。

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. )
  6. func main() {
  7. data, err := ioutil.ReadFile("/home/naveen/go/src/filehandling/test.txt")
  8. if err != nil {
  9. fmt.Println("File reading error", err)
  10. return
  11. }
  12. fmt.Println("Contents of file:", string(data))
  13. }

现在程序可以从任何位置运行,它将打印 test.txt 的内容。

例如,即使我从我的主目录运行它也会工作

  1. cd ~/Documents/filehandling
  2. go install
  3. cd ~
  4. filehandling

该程序将打印 test.txt 的内容

这似乎是一种简单的方法,但是文件应该位于程序中指定的路径中,否则此方法将失败。

2.将文件路径作为命令行标志传递

解决此问题的另一种方法是将文件路径作为命令行标志传递。使用 flag 包,我们可以从命令行获取文件路径作为输入,然后读取其内容。

让我们首先了解 flag 包的工作原理。flag 包具有 String function。此函数接受 3 个参数。第一个是 flag 的名称,第二个是默认值,第三个是 flag 的简短描述。

让我们编写一个小程序来从命令行中读取文件名。用以下内容替换 filehandling.go 的内容,


  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. )
  6. func main() {
  7. fptr := flag.String("fpath", "test.txt", "file path to read from")
  8. flag.Parse()
  9. fmt.Println("value of fpath is", *fptr)
  10. }

行号在上面的程序 8 中,创建一个名为 fpath 的字符串标志,其默认值为 test.txt,并使用 String 函数描述 file path to read from 。此函数返回存储标志值的字符串 variable

应该在程序访问任何标志之前调用 flag.Parse()

第 10 行我们打印标志的值

使用该命令运行此程序时

  1. wrkspacepath/bin/filehandling -fpath=/path-of-file/test.txt

我们将 /path-of-file/test.txt 作为标志 fpath 的值

程序输出

  1. value of fpath is /path-of-file/test.txt

如果程序仅使用 filehandling 而不传递任何 fpath,它将打印

  1. value of fpath is /path-of-file/test.txt

因为 test.txtfpath 的默认值。

现在我们知道如何从命令行读取文件路径,让我们继续完成我们的文件读取程序。

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "io/ioutil"
  6. )
  7. func main() {
  8. fptr := flag.String("fpath", "test.txt", "file path to read from")
  9. flag.Parse()
  10. data, err := ioutil.ReadFile(*fptr)
  11. if err != nil {
  12. fmt.Println("File reading error", err)
  13. return
  14. }
  15. fmt.Println("Contents of file:", string(data))
  16. }

上面的程序读取从命令行传递的文件路径的内容。使用该命令运行此程序

  1. wrkspacepath/bin/filehandling -fpath=/path-of-file/test.txt

请将 /path-of-file/ 替换为 test.txt 的实际路径。该程序将打印

  1. Contents of file: Hello World. Welcome to file handling in Go.

3.将文本文件与二进制文件捆绑在一起

从命令行获取文件路径的方法很好,但有一种更好的方法可以解决这个问题。如果我们能够将文本文件与二进制文件捆绑在一起,那会不会很棒。这就是我们接下来要做的事情。

有各种软件 packages 可以帮助我们实现这一目标。我们将使用 packr 因为它非常简单,我一直在使用它来完成我的项目而没有任何问题。

第一步是安装 packr 包。

在命令提示符中键入以下命令以安装该程序包


  1. go get -u github.com/gobuffalo/packr/...

packr 将静态文件(如 .txt)转换为 .go 文件,然后直接嵌入到二进制文件中。 Packer 非常智能,可以在开发过程中从磁盘而不是从二进制文件中获取静态文件。这样可以防止在只有静态文件发生变化时需要在开发期间重新编译。

用程序理解直观一些。用以下内容替换文件 filehandling.go 的内容,


  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gobuffalo/packr"
  5. )
  6. func main() {
  7. box := packr.NewBox("../filehandling")
  8. data := box.String("test.txt")
  9. fmt.Println("Contents of file:", data)
  10. }

上面的程序第 10 行,我们正在创建一个新 box。 box 表示一个文件夹,其内容将嵌入到二进制文件中。 在这种情况下,我指定包含 test.txtfilehandling 文件夹。 在下一行中,我们读取文件的内容并打印出来。

使用以下命令运行程序。

  1. cd ~/Documents/filehandling
  2. go install
  3. filehandling


程序将打印 test.txt 的内容。

由于我们现在处于开发阶段,文件将从磁盘读取。试着改变 test.txt 的内容,然后再次运行 filehandling。你可以看到程序打印出 test.txt 的更新内容,而不需要任何重新编译。完美:)。

Packr 还能够找到 box 的绝对路径。正因为如此,程序可以在任何目录下工作。它不需要 test.txt 存在于当前目录中。让我们 cd 到另一个目录,再试着运行程序。


  1. cd ~/Documents
  2. filehandling

运行上述命令也会打印出 test.txt 的内容。

现在让我们进入下一步,将 test.txt 捆绑到我们的二进制文件中。我们使用 packr2 命令来完成这项工作。

在 filehandling 目录下运行 packr2 命令。

  1. cd ~/Documents/filehandling/
  2. packr2

该命令将搜索新的 box 的源代码,并生成包含我们的 test.txt 文本文件的 Go 文件,并将其转换为字节,这可以与 Go 二进制文件一起捆绑。这个命令将生成一个文件 main-packr.go 和一个 packrd 包。这两个文件是需要和二进制文件一起捆绑的静态文件。

运行上述命令后,再次编译并运行程序。程序将打印 test.txt 的内容。


  1. go install
  2. filehandling

当运行 go install 时,你可能会得到以下错误。

  1. build filehandling: cannot load
  2. Users/naveen/Documents/filehandling/packrd: malformed module
  3. path "Users/naveen/Documents/filehandling/packrd": missing dot
  4. in first path element

这可能是因为 packr2 不知道我们正在使用 Go Modules。如果收到这个错误,请尝试通过运行命令 export GO111MODULE=on 将 Go modules 明确设置为 on。在将 Go modules 设置为 on 后,生成的文件必须被清理和重新生成。

  1. packr2 clean
  2. packr2
  3. go install
  4. filehandling

现在 test.txt 的内容将被打印出来,并且它正被从二进制中读取。

如果你怀疑这个文件是从二进制内部还是从磁盘上提供的,我建议你删除 test.txt,然后再次运行 filehandling 命令。你可以看到 test.txt 的内容被打印出来了。Awesome :D 我们已经成功地将静态文件嵌入到我们的二进制中。

一小块一小块读取文件

在上一节中,我们学习了如何将整个文件加载到内存中。当文件的大小非常大时,将整个文件读入内存是没有意义的,尤其是在内存不足的情况下。更理想的方法是以小块的形式读取文件。这可以在 bufio 包的帮助下完成。

让我们编写一个程序,以 3 个字节的块为单位读取 test.txt 文件。用以下内容替换filehandling.go 的内容,


  1. package main
  2. import (
  3. "bufio"
  4. "flag"
  5. "fmt"
  6. "log"
  7. "os"
  8. )
  9. func main() {
  10. fptr := flag.String("fpath", "test.txt", "file path to read from")
  11. flag.Parse()
  12. f, err := os.Open(*fptr)
  13. if err != nil {
  14. log.Fatal(err)
  15. }
  16. defer func() {
  17. if err = f.Close(); err != nil {
  18. log.Fatal(err)
  19. }
  20. }()
  21. r := bufio.NewReader(f)
  22. b := make([]byte, 3)
  23. for {
  24. _, err := r.Read(b)
  25. if err != nil {
  26. fmt.Println("Error reading file:", err)
  27. break
  28. }
  29. fmt.Println(string(b))
  30. }
  31. }

第 15 行中我们使用从命令行标志传递的路径打开文件。

第 19 行中我们 defer 关闭文件。

第 24 行程序创建一个新的缓冲 reader。 在下一行中,我们创建一个长度和容量为 3 的字节片,文件的字节将被读入其中。

第 27 行中的 Read 方法最多读取 len(b) 字节,即最多读取 3 个字节,并返回读取的字节数。我们将返回的字节数存储在一个变量 n 中。第 32 行中,切片从索引 0n-1,即最多读取 Read 方法返回的字节数,并打印出来。

一旦到达文件的末尾,它将返回一个 EOF 错误。剩下的程序就直接了当了。

如果我们使用命令运行上面的程序。


  1. cd ~/Documents/filehandling
  2. go install
  3. filehandling -fpath=/path-of-file/test.txt

将输出以下内容


  1. Hel
  2. lo
  3. Wor
  4. ld.
  5. We
  6. lco
  7. me
  8. to
  9. fil
  10. e h
  11. and
  12. lin
  13. g i
  14. n G
  15. o.
  16. Error reading file: EOF

逐行读取文件

在本节中,我们将讨论如何使用 Go 逐行读取文件。 这可以使用 bufio 包完成。

请用以下内容替换 test.txt 中的内容


  1. Hello World. Welcome to file handling in Go.
  2. This is the second line of the file.
  3. We have reached the end of the file.

以下是逐行读取文件所涉及的步骤。

  1. 打开文件


  1. 从文件中创建一个新的扫描程序


  1. 扫描文件并逐行读取

用以下内容替换 filehandling.go 的内容

  1. package main
  2. import (
  3. "bufio"
  4. "flag"
  5. "fmt"
  6. "log"
  7. "os"
  8. )
  9. func main() {
  10. fptr := flag.String("fpath", "test.txt", "file path to read from")
  11. flag.Parse()
  12. f, err := os.Open(*fptr)
  13. if err != nil {
  14. log.Fatal(err)
  15. }
  16. defer func() {
  17. if err = f.Close(); err != nil {
  18. log.Fatal(err)
  19. }
  20. }()
  21. s := bufio.NewScanner(f)
  22. for s.Scan() {
  23. fmt.Println(s.Text())
  24. }
  25. err = s.Err()
  26. if err != nil {
  27. log.Fatal(err)
  28. }
  29. }

第 15 行我们使用从命令行标志传递的路径打开文件。 第 24 行我们使用该文件创建一个新的 scanner。第 25 行 scan() 方法读取将通过 text() 方法可用的文件的下一行。

在 Scan 返回 false 之后,Err() 方法将返回扫描期间发生的任何错误,除非它是文件结束,Err() 将返回 nil

如果我们使用命令运行上面的程序

  1. cd ~/Documents/filehandling
  2. go install
  3. filehandling -fpath=/path-of-file/test.txt

文件的内容将逐行打印,如下所示。

  1. Hello World. Welcome to file handling in Go.
  2. This is the second line of the file.
  3. We have reached the end of the file.

原文链接

https://golangbot.com/read-files/