三十四.golang的读取文件
将整个文件读取到内存是最基本的文件操作之一。这需要使用 ioutil]包中的 ReadFile 函数。
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 上读取文件,因此请在你的本地环境运行这个程序。
请在 test.txt 所在的位置运行该程序。
例如,对于 linux/mac,如果 test.txt 位于 /home/naveen/go/src/filehandling,可以使用下列步骤来运行程序。
$ cd /home/naveen/go/src/filehandling/
$ go install filehandling
$ workspacepath/bin/filehandling
> cd C:\Users\naveen.r\go\src\filehandling > go install filehandling > workspacepath\bin\filehandling.exe
该程序会输出:
Contents of file: Hello World. Welcome to file handling in Go.
File reading error open test.txt: The system cannot find the file specified.
这是因为 Go 是编译型语言。go install
有三种方法可以解决这个问题。
-
使用绝对文件路径
-
使用命令行标记来传递文件路径
-
将文件绑定在二进制文件中
让我们来依次介绍。
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)) }
例如,可以在我的家目录运行。
$ cd $HOME
$ go install filehandling
$ workspacepath/bin/filehandling
该程序打印出了 test.txt 的内容。
另一种解决方案是使用命令行标记来传递文件路径。使用 flag
首先我们来看看 flag 包是如何工作的。flag 包有一个名为 String 的函数。该函数接收三个参数。第一个参数是标记名,第二个是默认值,第三个是标记的简短描述。
让我们来编写程序,从命令行读取文件名。将 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 行,通过 String 函数,创建了一个字符串标记,名称是
在程序访问 flag 之前,必须先调用 flag.Parse()。
在第 10 行,程序会打印出 flag 值。
使用下面命令运行程序。
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 的默认值是 test.txt。
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
Contents of file: Hello World. Welcome to file handling in Go.
有很多包可以帮助我们实现。我们会使用 packr,因为它很简单,并且我在项目中使用它时,没有出现任何问题。
第一步就是安装 packr 包。
在命令提示符中输入下面命令,安装 packr 包。
go get -u github.com/gobuffalo/packr/...
packr 会把静态文件(例如 .txt 文件)转换为 .go 文件,接下来,.go 文件会直接嵌入到二进制文件中。packer 非常智能,在开发过程中,可以从磁盘而非二进制文件中获取静态文件。在开发过程中,当仅仅静态文件变化时,可以不必重新编译。
我们通过程序来更好地理解它。用以下内容来替换 handling.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 行,我们创建了一个新盒子(New Box)。盒子表示一个文件夹,其内容会嵌入到二进制中。在这里,我指定了 filehandling 文件夹,其内容包含 test.txt。在下一行,我们读取了文件内容,并打印出来。
使用下面命令来运行程序。
go install filehandling
workspacepath/bin/filehandling
该程序会输出:
Contents of file: Hello World. Welcome to file handling in Go.
你可以试着改变 test.txt 的内容,然后再运行 filehandling
现在我们来看看如何将 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
该命令将静态文件绑定到了二进制文件中。
如果你不知道文件到底是由二进制还是磁盘来提供,我建议你删除 test.txt,并在此运行 filehandling 命令。你将看到,程序打印出了 test.txt 的内容。太棒了:D。我们已经成功将静态文件嵌入到了二进制文件中。
让我们来编写一个程序,以 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)) } }
在上述程序的第 15 行,我们使用命令行标记传递的路径,打开文件。
在第 19 行,我们延迟了文件的关闭操作。
第 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 来实现。
Hello World. Welcome to file handling in Go. This is the second line of the file. We have reached the end of the file.
逐行读取文件涉及到以下步骤。
-
打开文件;
-
在文件上新建一个 scanner;
-
扫描文件并且逐行读取。
将
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 行,我们用文件创建了一个新的 scanner。第 25 行的 Scan()
当 Scan 返回 false 时,除非已经到达文件末尾(此时 Err() 返回 nil),否则 Err() 就会返回扫描过程中出现的错误。
如果我使用下面命令来运行程序:
$ 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.