如何使用Go来读取文件

190 阅读11分钟

golang read files

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

本教程有以下几个部分:

  • 将整个文件读入内存
    • 使用绝对文件路径
    • 将文件路径作为一个命令行标志来传递
    • 将文件捆绑在二进制文件中
  • 以小块方式读取文件
  • 逐行读取文件

将整个文件读入内存

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

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

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

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

go mod init filehandling  

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

Hello World. Welcome to file handling in Go.  

这是我的目录结构

├── Documents
│   └── filehandling
│       ├── filehandling.go
|       ├── go.mod
│       └── test.txt

让我们马上开始写代码。创建一个文件filehandling.go ,内容如下

package main

import (  
    "fmt"
    "io/ioutil"
)

func main() {  
    data, err := ioutil.ReadFile("test.txt")
    if err != nil {
        fmt.Println("File reading error", err)
        return
    }
    fmt.Println("Contents of file:", string(data))
}

请在你的本地环境中运行这个程序,因为不可能在操场上读取文件。

第9行。上面程序的第9行读取文件并返回一个字节,该字节被存储在data 。14行我们将data 转换为string 并显示文件的内容。

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

如果test.txt位于~/Documents/filehandling,那么请按以下步骤运行此程序。

cd ~/Documents/filehandling/  
go install  
filehandling  

如果你不知道如何运行Go程序,请访问golangbot.com/hello-world…,了解更多。如果你想了解更多关于包和Go模块的信息,请访问golangbot.com/go-packages…

这个程序会打印出来。

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

如果从其他地方运行此程序,例如,尝试从以下地方运行此程序~/Documents/

cd ~/Documents/  
filehandling  

它将打印出以下错误。

File reading error open test.txt: no such file or directory  

原因是Go是一种编译语言。go install 所做的是,它从源代码中创建一个二进制文件。二进制文件是独立于源代码的,它可以从任何地方运行。由于在运行二进制文件的位置上找不到test.txt ,程序会抱怨说找不到指定的文件。

有三种方法来解决这个问题。

  1. 使用绝对文件路径
  2. 将文件路径作为一个命令行标志传递出去
  3. 将文本文件与二进制文件捆绑在一起

让我们逐一讨论这些方法。

1.使用绝对文件路径

解决这个问题的最简单方法是传递绝对文件路径。我已经修改了程序,在第9行将路径改为绝对路径。9.请把这个路径改为你的绝对位置test.txt

package main

import (  
    "fmt"
    "io/ioutil"
)

func main() {  
    data, err := ioutil.ReadFile("/home/naveen/Documents/filehandling/test.txt")
    if err != nil {
        fmt.Println("File reading error", err)
        return
    }
    fmt.Println("Contents of file:", string(data))
}

现在,该程序可以从任何地方运行,它将打印出test.txt 的内容。

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

cd ~/Documents/filehandling  
go install  
cd ~  
filehandling  

该程序将打印以下内容test.txt

这似乎是一个简单的方法,但也有一个缺陷,那就是文件应该位于程序中指定的路径中,否则这个方法会失败。

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

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

让我们先了解一下flag 包是如何工作的。flag 包有一个String 函数。这个函数接受3个参数。第一个是标志的名称,第二个是默认值,第三个是对该标志的简短描述。

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

package main  
import (  
    "flag"
    "fmt"
)

func main() {  
    fptr := flag.String("fpath", "test.txt", "file path to read from")
    flag.Parse()
    fmt.Println("value of fpath is", *fptr)
}

第8行。上述程序的第8行,使用String 函数创建一个名为fpath 的字符串标志,其默认值为test.txt ,描述为file path to read from 。该函数返回存储标志值的字符串变量的地址。

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

我们在第1行打印标志的值。10

当这个程序使用命令运行时

filehandling -fpath=/path-of-file/test.txt  

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

这个程序输出

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

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

value of fpath is test.txt  

因为test.txtfpath 的默认值。

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

package main  
import (  
    "flag"
    "fmt"
    "io/ioutil"
)

func main() {  
    fptr := flag.String("fpath", "test.txt", "file path to read from")
    flag.Parse()
    data, err := ioutil.ReadFile(*fptr)
    if err != nil {
        fmt.Println("File reading error", err)
        return
    }
    fmt.Println("Contents of file:", string(data))
}

上面的程序读取了从命令行传来的文件路径的内容。使用以下命令运行这个程序

filehandling -fpath=/path-of-file/test.txt  

请用test.txt 的绝对路径替换/path-of-file/ 。例如,在我的案例中,我运行了命令

filehandling --fpath=/home/naveen/Documents/filehandling/test.txt  

并打印了该程序。

Contents of file: Hello World. Welcome to file handling in Go.  
3.将文本文件与二进制文件捆绑在一起

上述从命令行获取文件路径的方案很好,但还有一个更好的方法来解决这个问题。如果我们能够将文本文件与二进制文件捆绑在一起,岂不妙哉?这就是我们接下来要做的。

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

第一步是安装packr

~/Documents/filehandling/ 目录下的命令提示符中键入以下命令来安装该包

cd ~/Documents/filehandling/  
go get -u github.com/gobuffalo/packr/v2/...  

packr将静态文件(如.txt )转换为.go 文件,然后直接嵌入二进制文件中。Packer足够聪明,在开发过程中从磁盘而不是从二进制文件中获取静态文件。这就避免了在开发过程中只有静态文件发生变化时需要重新编译的问题。

一个程序会让我们更好地理解事情。将filehandling.go 的内容替换为以下内容。

package main

import (  
    "fmt"

    "github.com/gobuffalo/packr/v2"
)

func main() {  
        box :=  packr.New("fileBox", "../filehandling")
        data, err := box.FindString("test.txt")
        if err != nil {
            fmt.Println("File reading error", err)
                return
        }
        fmt.Println("Contents of file:", data)
}

在上面的程序的第10行中,我们要做的是在上述程序的第10行,我们正在创建一个名为box 的新盒子。一个盒子代表一个文件夹,其内容将被嵌入二进制文件中。在本例中,我指定的是filehandling 文件夹,其中包含test.txt 。在下一行中,我们使用FindString 方法读取文件的内容并打印。

使用以下命令运行该程序。

cd ~/Documents/filehandling  
go install  
filehandling  

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

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

Packr也能够找到盒子的绝对路径。正因为如此,该程序将在任何目录下工作。它不需要test.txt 存在于当前目录中。让我们cd到一个不同的目录,并再次尝试运行该程序。

cd ~/Documents  
filehandling  

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

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

filehandling 目录下运行packr2 命令。

cd ~/Documents/filehandling/  
packr2  

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

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

go install  
filehandling  

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

build filehandling: cannot load Users/naveen/Documents/filehandling/packrd: malformed module path "Users/naveen/Documents/filehandling/packrd": missing dot in first path element  

这可能是因为 packr2 不知道我们在使用 Go 模块。如果你遇到这个错误,请尝试通过运行export GO111MODULE=on 命令将 Go 模块明确设置为 on 。在将 Go 模块设置为 on 之后,必须对生成的文件进行清理并重新生成。

packr2 clean  
packr2  
go install  
filehandling  

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

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

读取一个小块的文件

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

让我们写一个程序,以3个字节为一个小块读取我们的test.txt 文件。运行packr2 clean ,删除上一节中由packr生成的文件。你可能还想重新创建test.txt ,以防你删除它。用以下内容替换filehandling.go 的内容。

package main

import (  
    "bufio"
    "flag"
    "fmt"
    "log"
    "os"
)

func main() {  
    fptr := flag.String("fpath", "test.txt", "file path to read from")
    flag.Parse()

    f, err := os.Open(*fptr)
    if err != nil {
        log.Fatal(err)
    }
    defer func() {
        if err = f.Close(); err != nil {
            log.Fatal(err)
        }
    }()
    r := bufio.NewReader(f)
    b := make([]byte, 3)
    for {
        n, err := r.Read(b)
        if err != nil {
            fmt.Println("Error reading file:", err)
            break
        }
        fmt.Println(string(b[0:n]))
    }
}

在上述程序的第15行中,我们打开15行中,我们使用命令行标志传递的路径打开文件。

在第19行中,我们推迟关闭文件。19,我们推迟了文件的关闭。

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

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

一旦到达文件的末端,它将返回一个EOF错误。该程序的其余部分是直截了当的。

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

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

将会有以下输出

Hel  
lo  
Wor  
ld.  
 We
lco  
me  
to  
fil  
e h  
and  
lin  
g i  
n G  
o.  
Error reading file: EOF  

逐行读取文件

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

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

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

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

  1. 打开文件
  2. 从该文件中创建一个新的扫描器
  3. 扫描文件并逐行读取。

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

package main

import (  
    "bufio"
    "flag"
    "fmt"
    "log"
    "os"
)

func main() {  
    fptr := flag.String("fpath", "test.txt", "file path to read from")
    flag.Parse()

    f, err := os.Open(*fptr)
    if err != nil {
        log.Fatal(err)
    }
    defer func() {
        if err = f.Close(); err != nil {
        log.Fatal(err)
    }
    }()
    s := bufio.NewScanner(f)
    for s.Scan() {
        fmt.Println(s.Text())
    }
    err = s.Err()
    if err != nil {
        log.Fatal(err)
    }
}

在第在上述程序的第15行,我们使用命令行标志传递的路径打开文件。在第24行,我们使用该文件创建一个新的扫描仪。第25行的scan() 方法读取文件的下一行,所读取的字符串将通过text() 方法获得。

在扫描器返回falseErr() 方法将返回扫描过程中发生的任何错误。如果错误是End of File,Err() 将返回nil

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

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

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

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

这样我们就到了本教程的结尾。希望你喜欢它。请留下您的评论。