Go中读取文件

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

本教程有以下部分。

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

将整个文件读入内存

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

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

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

filehandling通过从目录运行以下命令来创建一个 Go 模块filehandling

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"
	"os"
)

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

请从您的本地环境运行此程序,因为无法读取 Playground 中的文件。

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

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

如果test.txt位于**~/Documents/filehandling**,则使用以下步骤运行该程序,

cd ~/Documents/filehandling/
go install 
filehandling

该程序将打印

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.使用绝对文件路径

解决这个问题最简单的方法是传递绝对文件路径。我修改了程序,并将第 1 行中的路径更改为绝对路径。9. 请将此路径更改为您的test.txt.

package main

import (
	"fmt"
	"os"
)

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

现在,该程序可以从任何位置运行,它将打印 的内容。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 行,使用该函数创建一个以默认值和描述命名的字符串标志。此函数返回存储标志值的字符串变量的地址。

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

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

使用此命令运行此程序时

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

我们/path-of-file/test.txt作为 flag 的值传递fpath

该程序输出

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

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

value of fpath is test.txt

因为test.txt是 的默认值fpath

flag还提供了可用的不同参数的格式良好的输出。这可以通过运行来显示

filehandling --help

该命令将打印以下输出。

Usage of filehandling:
  -fpath string
    	file path to read from (default "test.txt")

很好不是吗:)。

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

package main

import (
	"flag"
	"fmt"
	"os"
)

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

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

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

请替换/path-of-file/为 的绝对路径test.txt。例如,就我而言,我运行了命令

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

并打印出程序。

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

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

标准库中的嵌入包将帮助我们实现这一目标

导入embed包后,//go:embed可以使用该指令读取文件的内容。

程序将使我们更好地理解事物。

将 的内容替换filehandling.go为以下内容,

package main

import (
	_ "embed"
	"fmt"
)

//go:embed test.txt
var contents []byte

func main() {
	fmt.Println("Contents of file:", string(contents))
}

在上面程序的第 4 行中,我们导入带有下划线前缀的包。原因是因为它没有在代码中显式使用,但第 8 行中的注释需要编译器进行一些预处理。由于我们需要在没有任何显式用法的情况下导入包,因此我们用下划线作为前缀以使编译器满意。如果没有,编译器将抱怨该包未在任何地方使用

第 8 行告诉编译器读取该注释后面的变量的内容并将其赋值。在我们的例子中,变量将保存文件的内容。//go:embed test.txt``test.txt``contents

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

cd ~/Documents/filehandling
go install
filehandling

程序将打印

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

现在,该文件与二进制文件捆绑在一起,无论从何处执行,它都可供 go 二进制文件使用。例如,尝试test.txt从不驻留的目录运行该程序。

cd ~/Documents
filehandling

上面的命令还将打印文件的内容。

请注意,应分配文件内容的变量必须位于包级别。局部变量将不起作用。尝试将程序更改为以下内容。

package main

import (
	_ "embed"
	"fmt"
)

func main() {
	//go:embed test.txt
	var contents []byte
	fmt.Println("Contents of file:", string(contents))
}

上面的程序有contents一个局部变量。

该程序现在将无法编译并出现以下错误。

./filehandling.go:9:4: go:embed cannot apply to var inside func

分块读取文件

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

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

package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"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 == io.EOF {
			fmt.Println("finished reading file")
			break
		}
		if err != nil {
			fmt.Printf("Error %s reading file", err)
			break
		}
		fmt.Println(string(b[0:n]))
	}
}

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

在第 20 行中,我们推迟文件关闭。

上面程序的第 26 行创建了一个新的缓冲读取器。在下一行中,我们创建一个长度和容量为 3 的字节切片,文件的字节将被读取到其中。

第 29 行中的方法最多读取字节,即最多 3 个字节,并返回读取的字节数。我们将返回的字节存储在变量中。在第 38 行中,切片从索引读取到 ,即最多读取方法返回并打印的字节数。

到达文件末尾后,将返回 EOF 错误。我们在第 30 行检查此错误。程序的其余部分是直截了当的。

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

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.
finished reading file

逐行读取文件

在本节中,我们将讨论如何使用 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 返回后,该方法将返回扫描过程中发生的任何错误。如果错误为“文件末尾”,将返回 .false``Err()``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.

本教程到此结束。希望你喜欢它。请留下您的评论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值