读取文件
将整个文件读入内存
最基本的文件操作之一是将整个文件读入内存。 这是在ioutil
包的ReadFile
函数的帮助下完成的。
让我们从go程序所在的目录中读取一个文件。 我已经在我的GOROOT
内部创建了一个文件夹filehandling
,我在里面有一个文本文件test.txt
,它将使用我们的Go程序filehandling.go
来读取。 test.txt
包含文本“Hello World,欢迎使用Go中的文件处理。”。 这是我的文件夹结构。
src
filehandling
filehandling.go
test.txt
让我们马上看看代码吧。
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))
}
请从本地环境运行此程序,因为无法在playground上读取文件。
上述程序第9行中读取文件并返回存储在数据中的字节片。我们将数据转换为字符串并显示文件的内容。
请从test.txt所在的位置运行该程序。
例如,在linux/mac
的情况下,如果test.txt
位于home/naveen/go/src/filehandling
,则使用以下步骤运行该程序,
$]cd /home/naveen/go/src/filehandling/
$]go install filehandling
$]workspacepath/bin/filehandling
对于windows,如果test.txt
位于C:\Users\naveen.r\go\src\filehandling
,则使用以下步骤运行此程序,
Contents of file: Hello World. Welcome to file handling in Go.
如果从任何其他位置运行此程序,例如尝试从/home/userdirectory
运行该程序,它将打印以下错误。
File reading error open test.txt: The system cannot find the file specified.
原因是Go是一种编译语言。 安装的作用是,它从源代码创建二进制文件。 二进制文件独立于源代码,可以从任何位置运行。 由于在运行二进制文件的位置找不到test.txt,程序会报错它无法找到指定的文件。
有三种方法可以解决这个问题,
- 使用绝对文件路径
- 将文件路径作为命令行标志传递
- 将文本文件与二进制文件捆绑在一起
让我们一个一个讨论。
1.使用绝对文件路径
解决此问题的最简单方法是传递绝对文件路径。 我修改了程序并将路径更改为绝对路径。
package main
import (
"fmt"
"io/ioutil"
)
func main() {
data, err := ioutil.ReadFile("/home/naveen/go/src/filehandling/test.txt")
if err != nil {
fmt.Println("File reading error", err)
return
}
fmt.Println("Contents of file:", string(data))
}
现在程序可以从任何位置运行,它将打印test.txt的内容。
例如,即使我从我的主目录运行它也会工作
$]cd $HOME
$]go install filehandling
$]workspacepath/bin/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行中,创建一个名为fpath
的字符串标志,其默认值为test.txt
,并使用String
函数读取描述文件路径。 此函数返回存储标志值的字符串变量的地址。
应该在程序访问任何标志之前调用flag.Parse()
。
我们在第一行打印标志的值。
使用该命令运行此程序时
wrkspacepath/bin/filehandling -fpath=/path-of-file/test.txt
我们将/path-of-file/test.txt
作为标志fpath
的值传递。
该程序输出
value of fpath is /path-of-file/test.txt
如果程序仅使用文件处理运行而不传递任何fpath
,它将打印
value of fpath is test.txt
因为test.txt是fpath的默认值。
现在我们知道如何从命令行读取文件路径,让我们继续完成我们的文件读取程序。
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))
}
上面的程序读取从命令行传递的文件路径的内容。 使用该命令运行此程序
wrkspacepath/bin/filehandling -fpath=/path-of-file/test.txt
请将/path-of-file/
替换为test.txt
的实际路径。 该程序将打印
Contents of file: Hello World. Welcome to file handling in Go.
3.将文本文件与二进制文件捆绑在一起
从命令行获取文件路径的上述选项很好,但有一种更好的方法可以解决这个问题。 如果我们能够将文本文件与二进制文件捆绑在一起,那会不会很棒。 这就是我们接下来要做的事情。
有各种软件包可以帮助我们实现这一目标。 我们将使用packr
,因为它非常简单,我一直在使用它在我的项目里没有任何问题。
第一步是安装packr
包。
在命令提示符中键入以下命令以安装该程序包
go get -u github.com/gobuffalo/packr/...
packr
将静态文件(如.txt)转换为.go文件,然后直接嵌入到二进制文件中。 Packer
非常智能,可以在开发过程中从磁盘而不是从二进制文件中获取静态文件。 这样可以防止在只有静态文件发生变化时需要在开发期间重新编译。
一个程序将使我们更好地理解事物。 用以下内容替换filehandling.go的内容,
package main
import (
"fmt"
"github.com/gobuffalo/packr"
)
func main() {
box := packr.NewBox("../filehandling")
data := box.String("test.txt")
fmt.Println("Contents of file:", data)
}
在上面的第10行程序,我们正在创建一个新盒子。盒子表示一个文件夹,其内容将嵌入到二进制文件中。 在这种情况下,我指定包含test.txt
的filehandling
文件夹。 在下一行中,我们读取文件的内容并打印出来。
当我们处于开发阶段时,我们可以使用go install
命令来运行该程序。 它将按预期工作。 packr
足够智能,可以在开发阶段从磁盘加载文件。
使用以下命令运行该程序。
go install filehandling
workspacepath/bin/filehandling
这些命令可以从任何位置运行。 Packr
足够智能,可以获取传递给NewBox命令的目录的绝对路径。
这个程序将打印出来
Contents of file: Hello World. Welcome to file handling in Go.
尝试更改test.txt
的内容并再次运行文件处理。 您可以看到该程序打印test.txt
的更新内容,而无需任何重新编译。完美。
现在让我们将test.txt
捆绑到我们的二进制文件中。 我们使用packr
命令来执行此操作。
运行以下命令
packr install -v filehandling
这将打印出来
building box ../filehandling
packing file filehandling.go
packed file filehandling.go
packing file test.txt
packed file test.txt
built box ../filehandling with ["filehandling.go" "test.txt"]
filehandling
此命令将静态文件与二进制文件捆绑在一起。
运行上述命令后,使用命令workspacepath/bin/filehandling
运行程序。 该程序将打印test.txt
的内容。 现在正在从二进制文件中读取test.txt
。
如果您怀疑文件是从二进制文件还是从磁盘提供,我建议您删除test.txt
并再次运行命令文件句柄。 您可以看到test.txt的内容已打印出来。 太棒了,我们已成功将静态文件嵌入到二进制文件中。
以小块读取文件
在上一节中,我们学习了如何将整个文件加载到内存中。 当文件的大小非常大时,将整个文件读入内存是没有意义的,尤其是在RAM不足的情况下。 更优化的方法是以小块读取文件。 这可以在bufio
包的帮助下完成。
让我们编写一个程序,以3个字节的块为单位读取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 {
_, err := r.Read(b)
if err != nil {
fmt.Println("Error reading file:", err)
break
}
fmt.Println(string(b))
}
}
在上面的程序中,我们使用从命令行标志传递的路径打开文件。
在第19行我们延迟关闭文件。
上面的程序第24行中创建了一个新的缓冲读取器。 在下一行中,我们创建一个长度和容量为3的字节片,文件的字节将被读入其中。
第27行中的Read方法读取len(b)
字节,即最多3个字节,并返回读取的字节数。 一旦到达文件末尾,它将返回EOF错误。
如果我们使用命令运行上面的程序,
$]go install filehandling
$]wrkspacepath/bin/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.
以下是逐行读取文件所涉及的步骤。
- 打开文件
- 从文件中创建一个新的扫描程序
- 扫描文件并逐行读取。
用以下内容替换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)
}
}
在上面的程序中,我们使用从命令行标志传递的路径打开文件。在第24行我们使用该文件创建一个新的扫描器。第25行为scan()
的方法。通过text()
方法可读取文件的下一行。
在Scan
返回false之后,Err()
方法将返回扫描期间发生的任何错误,除非文件读取结束,Err()
将返回nil
。
如果我们使用命令运行上面的程序,
$] go install filehandling
$] workspacepath/bin/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.