Go语言中的数据输入输出
在Go语言中,数据的输入输出(IO)操作是非常重要的一部分,它涉及到文件读写、标准输入输出、格式化IO等多个方面。下面将详细介绍Go语言中数据IO的相关内容。
1. 使用
io
包
io
包是Go语言中处理IO操作的基础包,它定义了
io.Reader
和
io.Writer
接口,用于表示输入和输出的基本操作。以下是
io
包中一些常用的函数和类型:
| 函数/类型 | 描述 |
| — | — |
|
io.Copy()
| 该函数(及其变体
io.CopyBuffer
和
io.CopyN
)可以轻松地将数据从任意的
io.Reader
源复制到任意的
io.Writer
目标。示例代码如下:
data := strings.NewReader("Write me down.")
file, _ := os.Create("./iocopy.data")
io.Copy(file, data)
|
PipeReader
和
PipeWriter
|
io
包中的
PipeReader
和
PipeWriter
类型将IO操作建模为内存中的管道。数据写入管道的
io.Writer
,并可以独立地从管道的
io.Reader
读取。示例代码如下:
file, _ := os.Create("./iopipe.data")
pr, pw := io.Pipe()
go func() {
fmt.Fprint(pw, "Pipe streaming")
pw.Close()
}()
wait := make(chan struct{})
go func() {
io.Copy(file, pr)
pr.Close()
close(wait)
}()
<-wait //wait for pr to finish
需要注意的是,管道写入器会阻塞,直到读取器完全消耗完管道内容或遇到错误。因此,为了避免死锁,读取器和写入器都应该封装在goroutine中。
|
io.TeeReader()
| 类似于
io.Copy
函数,
io.TeeReader
将内容从读取器传输到写入器。不过,该函数还会通过返回的
io.Reader
发出复制的字节(未改变)。
TeeReader
适用于组合多步骤的IO流处理。示例代码如下:
fin, _ := os.Open("./ioteerdr.go")
defer fin.Close()
fout, _ := os.Create("./teereader.gz")
defer fout.Close()
zip := gzip.NewWriter(fout)
defer zip.Close()
sha := sha1.New()
data := io.TeeReader(fin, sha)
io.Copy(zip, data)
fmt.Printf("SHA1 hash %x\n", sha.Sum(nil))
如果要同时计算SHA - 1和MD5,可以嵌套两个
TeeReader
值,示例代码如下:
sha := sha1.New()
md := md5.New()
data := io.TeeReader(
io.TeeReader(fin, md), sha,
)
io.Copy(zip, data)
|
io.WriteString()
| 该函数将字符串的内容写入指定的写入器。示例代码如下:
fout, err := os.Create("./iowritestr.data")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer fout.Close()
io.WriteString(fout, "Hello there!\n")
|
io.LimitedReader
| 该结构体是一个读取器,它只从指定的
io.Reader
读取N个字节。示例代码如下:
str := strings.NewReader("The quick brown " +
"fox jumps over the lazy dog")
limited := &io.LimitedReader{R: str, N: 19}
io.Copy(os.Stdout, limited)
运行上述代码将输出:
The quick brown fox
|
io.SectionReader
| 该类型通过指定读取的起始索引(从0开始)和要读取的字节数偏移值来实现查找和跳过操作。示例代码如下:
str := strings.NewReader("The quick brown"+
"fox jumps over the lazy dog")
section := io.NewSectionReader(str, 19, 23)
io.Copy(os.Stdout, section)
运行上述代码将输出:
jumps over the lazy dog
2.
io/ioutil
子包
io/ioutil
子包实现了一些实用函数,为文件读取、目录列表、临时目录创建和文件写入等IO基本操作提供了便捷的方法。
3. 文件操作
os
包中的
os.File
类型表示系统上的文件句柄,它实现了多个IO基本操作,包括
io.Reader
和
io.Writer
接口,因此可以使用标准的流式IO API处理文件内容。
3.1 创建和打开文件
-
os.Create函数用于创建一个具有指定路径的新文件。如果文件已经存在,os.Create将覆盖它。 -
os.Open函数用于打开一个现有文件进行读取。
以下是一个打开现有文件并复制其内容的示例代码:
func main() {
f1, err := os.Open("./file0.go")
if err != nil {
fmt.Println("Unable to open file:", err)
os.Exit(1)
}
defer f1.Close()
f2, err := os.Create("./file0.bkp")
if err != nil {
fmt.Println("Unable to create file:", err)
os.Exit(1)
}
defer f2.Close()
n, err := io.Copy(f2, f1)
if err != nil {
fmt.Println("Failed to copy:", err)
os.Exit(1)
}
fmt.Printf("Copied %d bytes from %s to %s\n",
n, f1.Name(), f2.Name())
}
3.2
os.OpenFile
函数
os.OpenFile
函数提供了通用的底层功能,用于创建新文件或打开现有文件,并可以对文件的行为和权限进行细粒度控制。不过,通常更常用
os.Open
和
os.Create
函数,因为它们提供了比
os.OpenFile
函数更简单的抽象。
os.OpenFile
函数接受三个参数:文件路径、表示操作行为的掩码位字段值(例如,只读、读写、截断等)和符合POSIX标准的文件权限值。以下是使用
os.OpenFile
函数重新实现文件复制的示例代码:
func main() {
f1, err := os.OpenFile("./file0.go", os.O_RDONLY, 0666)
if err != nil {...}
defer f1.Close()
f2, err := os.OpenFile("./file0.bkp", os.O_WRONLY, 0666)
if err != nil {...}
defer f2.Close()
n, err := io.Copy(f2, f1)
if err != nil {...}
fmt.Printf("Copied %d bytes from %s to %s\n",
n, f1.Name(), f2.Name())
}
如果已经有一个操作系统文件描述符的引用,还可以使用
os.NewFile
函数在程序中创建文件句柄。不过,
os.NewFile
函数很少使用,因为文件通常使用前面讨论的文件函数进行初始化。
3.3 文件读写
-
写入文件
:除了使用
os.Copy函数移动数据外,还可以使用os.File变量的WriteString方法创建文本文件。示例代码如下:
func main() {
rows := []string{
"The quick brown fox",
"jumps over the lazy dog",
}
fout, err := os.Create("./filewrite.data")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer fout.Close()
for _, row := range rows {
fout.WriteString(row)
}
}
如果数据来源不是文本,可以直接将原始字节写入文件,示例代码如下:
func main() {
data := [][]byte{
[]byte("The quick brown fox\n"),
[]byte("jumps over the lazy dog\n"),
}
fout, err := os.Create("./filewrite.data")
if err != nil { ... }
defer fout.Close()
for _, out := range data {
fout.Write(out)
}
}
-
读取文件
:作为
io.Reader,可以直接使用Read方法从os.File类型读取文件内容,将其作为原始字节切片流访问。示例代码如下:
func main() {
fin, err := os.Open("../ch05/dict.txt")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer fin.Close()
p := make([]byte, 1024)
for {
n, err := fin.Read(p)
if err == io.EOF {
break
}
fmt.Print(string(p[:n]))
}
}
4. 标准输入、输出和错误
os
包包含三个预声明的变量
os.Stdin
、
os.Stdout
和
os.Stderr
,分别表示操作系统的标准输入、输出和错误的文件句柄。以下是一个读取文件并将其内容写入标准输出的示例代码:
func main() {
f1, err := os.Open("./file0.go")
if err != nil {
fmt.Println("Unable to open file:", err)
os.Exit(1)
}
defer f1.Close()
n, err := io.Copy(os.Stdout, f1)
if err != nil {
fmt.Println("Failed to copy:", err)
os.Exit(1)
}
fmt.Printf("Copied %d bytes from %s \n", n, f1.Name())
}
5. 使用
fmt
包进行格式化IO
fmt
包是Go语言中广泛使用的IO包,它提供了一系列用于格式化输入和输出的函数。
5.1 向
io.Writer
接口打印
fmt
包提供了几个函数,用于将文本数据写入任意的
io.Writer
实现。
fmt.Fprint
和
fmt.Fprintln
函数以默认格式写入文本,而
fmt.Fprintf
支持格式说明符。以下是一个使用
fmt.Fprintf
函数将类金属数据以列格式写入指定文本文件的示例代码:
type metalloid struct {
name string
number int32
weight float64
}
func main() {
var metalloids = []metalloid{
{"Boron", 5, 10.81},
...
{"Polonium", 84, 209.0},
}
file, _ := os.Create("./metalloids.txt")
defer file.Close()
for _, m := range metalloids {
fmt.Fprintf(
file,
"%-10s %-10d %-10.3f\n",
m.name, m.number, m.weight,
)
}
}
5.2 向标准输出打印
fmt.Print
、
fmt.Printf
和
fmt.Println
函数与前面的
Fprint
系列函数具有相同的特性,不同的是它们将文本写入标准输出文件句柄
os.Stdout
。以下是一个将类金属列表写入标准输出的示例代码:
type metalloid struct { ... }
func main() {
var metalloids = []metalloid{
{"Boron", 5, 10.81},
...
{"Polonium", 84, 209.0},
}
for _, m := range metalloids {
fmt.Printf(
"%-10s %-10d %-10.3f\n",
m.name, m.number, m.weight,
)
}
}
5.3 从
io.Reader
读取
fmt
包还支持从
io.Reader
接口格式化读取文本数据。
fmt.Fscan
和
fmt.Fscanln
函数可用于将多个以空格分隔的值读取到指定的参数中。
fmt.Fscanf
函数支持格式说明符,用于更丰富和灵活地解析来自
io.Reader
实现的数据输入。以下是一个使用
fmt.Fscanf
函数格式化输入包含行星数据的空格分隔文件(
planets.txt
)的示例代码:
func main() {
var name, hasRing string
var diam, moons int
// read data
data, err := os.Open("./planets.txt")
if err != nil {
fmt.Println("Unable to open planet data:", err)
return
}
defer data.Close()
for {
_, err := fmt.Fscanf(
data,
"%s %d %d %s\n",
&name, &diam, &moons, &hasRing,
)
if err != nil {
if err == io.EOF {
break
} else {
fmt.Println("Scan error:", err)
return
}
}
fmt.Printf(
"%-10s %-10d %-6d %-6s\n",
name, diam, moons, hasRing,
)
}
}
5.4 从标准输入读取
fmt.Scan
、
fmt.Scanf
和
fmt.Scanln
函数用于从标准输入文件句柄
os.Stdin
读取数据。以下是一个从控制台读取文本输入的简单程序示例代码:
func main() {
var choice int
fmt.Println("A square is what?")
fmt.Print("Enter 1=quadrilateral 2=rectagonal:")
n, err := fmt.Scanf("%d", &choice)
if n != 1 || err != nil {
fmt.Println("Follow directions!")
return
}
if choice == 1 {
fmt.Println("You are correct!")
} else {
fmt.Println("Wrong, Google it.")
}
}
6. 缓冲IO
到目前为止,大多数IO操作都是无缓冲的,这意味着每次读写操作可能会受到底层操作系统处理IO请求延迟的负面影响。而缓冲操作通过在IO操作期间将数据缓冲在内部内存中,减少了延迟。
bufio
包提供了用于缓冲读写IO操作的函数。
6.1 缓冲写入器和读取器
-
缓冲写入
:
bufio包提供了几个函数,用于使用io.Writer接口对IO流进行缓冲写入。以下是一个使用缓冲IO创建文本文件并写入内容的示例代码:
func main() {
rows := []string{
"The quick brown fox",
"jumps over the lazy dog",
}
fout, err := os.Create("./filewrite.data")
writer := bufio.NewWriter(fout)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer fout.Close()
for _, row := range rows {
writer.WriteString(row)
}
writer.Flush()
}
一般来说,
bufio
包中的构造函数通过包装现有的
io.Writer
作为其底层源来创建缓冲写入器。例如,上述代码使用
bufio.NewWriter
函数包装
io.File
变量
fout
创建了一个缓冲写入器。如果要影响内部缓冲区的大小,可以使用
bufio.NewWriterSize(w io.Writer, n int)
构造函数指定内部缓冲区大小。
bufio.Writer
类型还提供了
Write
和
WriteByte
方法用于写入原始字节,以及
WriteRune
方法用于写入Unicode编码字符。
-
缓冲读取
:读取缓冲流可以通过调用
bufio.NewReader
构造函数包装现有的
io.Reader
来实现。以下是一个创建
bufio.Reader
变量并读取文本文件的示例代码:
func main() {
file, err := os.Open("./bufread0.go")
if err != nil {
fmt.Println("Unable to open file:", err)
return
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
break
} else {
fmt.Println("Error reading:, err")
return
}
}
fmt.Print(line)
}
}
上述代码使用
reader.ReadString
方法以
\n
字符作为内容分隔符读取文本文件。如果要影响内部缓冲区的大小,可以使用
bufio.NewReaderSize(w io.Reader, n int)
构造函数指定内部缓冲区大小。
bufio.Reader
类型还提供了
Read
、
ReadByte
和
ReadBytes
方法用于从流中读取原始字节,以及
ReadRune
方法用于读取Unicode编码字符。
6.2 扫描缓冲区
bufio
包还提供了用于扫描和标记来自
io.Reader
源的缓冲输入数据的基本操作。
bufio.Scanner
类型使用
Split
方法定义标记化策略来扫描输入数据。以下是一个使用
bufio.Scanner
重新实现行星示例的代码:
func main() {
file, err := os.Open("./planets.txt")
if err != nil {
fmt.Println("Unable to open file:", err)
return
}
defer file.Close()
fmt.Printf(
"%-10s %-10s %-6s %-6s\n",
"Planet", "Diameter", "Moons", "Ring?",
)
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
fields := strings.Split(scanner.Text(), " ")
fmt.Printf(
"%-10s %-10s %-6s %-6s\n",
fields[0], fields[1], fields[2], fields[3],
)
}
}
使用
bufio.Scanner
的步骤如下:
1. 使用
bufio.NewScanner(io.Reader)
创建一个扫描器。
2. 调用
scanner.Split
方法配置内容的标记化方式。
3. 使用
scanner.Scan
方法遍历生成的标记。
4. 使用
scanner.Text
方法读取标记化的数据。
bufio
包提供了几个预定义的分割器函数,包括
ScanBytes
(将每个字节作为一个标记扫描)、
ScanRunes
(扫描UTF - 8编码的标记)和
ScanWords
(将每个以空格分隔的单词作为标记扫描)。
7. 内存中的IO
bytes
包提供了常见的基本操作,用于对存储在内存中的字节块进行流式IO,由
bytes.Buffer
类型表示。由于
bytes.Buffer
类型实现了
io.Reader
和
io.Writer
接口,因此它是使用流式IO基本操作将数据流入或流出内存的理想选择。以下是一个将多个字符串值存储在
byte.Buffer
变量中,然后将缓冲区流式传输到
os.Stdout
的示例代码:
func main() {
var books bytes.Buffer
books.WriteString("The Great Gatsby")
books.WriteString("1984")
// 可以继续添加更多字符串
io.Copy(os.Stdout, &books)
}
通过以上介绍,我们可以看到Go语言提供了丰富的工具和函数,用于处理各种数据IO操作,无论是文件读写、标准输入输出,还是格式化IO和缓冲IO,都能方便地实现。
Go语言中的数据输入输出(续)
8. 总结与操作建议
在前面的内容中,我们详细介绍了Go语言中数据IO的各个方面,下面对不同场景下的操作选择进行总结,并给出具体的操作建议。
8.1 文件操作总结
-
文件创建与打开
:
-
若只是简单创建新文件,优先使用
os.Create函数,它会覆盖已存在的同名文件。操作步骤为:调用os.Create函数并传入文件路径,如fout, err := os.Create("./filewrite.data"),若有错误处理错误,使用完文件后通过defer fout.Close()关闭文件。 -
若仅为读取现有文件,使用
os.Open函数,操作步骤与创建文件类似,如fin, err := os.Open("./file0.go")。 -
当需要对文件行为和权限进行细粒度控制时,使用
os.OpenFile函数。操作步骤为:传入文件路径、操作行为掩码位字段值和文件权限值,如f1, err := os.OpenFile("./file0.go", os.O_RDONLY, 0666),后续同样要处理错误和关闭文件。
-
若只是简单创建新文件,优先使用
-
文件读写
:
-
若要将大量数据从一个文件复制到另一个文件,使用
io.Copy函数,示例代码如下:
-
若要将大量数据从一个文件复制到另一个文件,使用
f1, err := os.Open("./file0.go")
if err != nil {
fmt.Println("Unable to open file:", err)
os.Exit(1)
}
defer f1.Close()
f2, err := os.Create("./file0.bkp")
if err != nil {
fmt.Println("Unable to create file:", err)
os.Exit(1)
}
defer f2.Close()
n, err := io.Copy(f2, f1)
if err != nil {
fmt.Println("Failed to copy:", err)
os.Exit(1)
}
fmt.Printf("Copied %d bytes from %s to %s\n", n, f1.Name(), f2.Name())
- 若要逐行写入文本数据到文件,可使用`os.File`的`WriteString`方法,如前面的示例:
rows := []string{
"The quick brown fox",
"jumps over the lazy dog",
}
fout, err := os.Create("./filewrite.data")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer fout.Close()
for _, row := range rows {
fout.WriteString(row)
}
- 若要读取文件内容,可使用`os.File`的`Read`方法按字节块读取,示例代码如下:
fin, err := os.Open("../ch05/dict.txt")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer fin.Close()
p := make([]byte, 1024)
for {
n, err := fin.Read(p)
if err == io.EOF {
break
}
fmt.Print(string(p[:n]))
}
8.2 格式化IO总结
-
向
io.Writer写入格式化数据 :使用fmt.Fprintf函数,它支持格式说明符。操作步骤为:定义要写入的数据结构,创建io.Writer对象(如文件),遍历数据并使用fmt.Fprintf写入,示例代码如下:
type metalloid struct {
name string
number int32
weight float64
}
func main() {
var metalloids = []metalloid{
{"Boron", 5, 10.81},
// ...
{"Polonium", 84, 209.0},
}
file, _ := os.Create("./metalloids.txt")
defer file.Close()
for _, m := range metalloids {
fmt.Fprintf(
file,
"%-10s %-10d %-10.3f\n",
m.name, m.number, m.weight,
)
}
}
-
从
io.Reader读取格式化数据 :使用fmt.Fscanf函数,它支持格式说明符。操作步骤为:打开io.Reader对象(如文件),定义接收数据的变量,使用fmt.Fscanf按格式读取数据,示例代码如下:
func main() {
var name, hasRing string
var diam, moons int
data, err := os.Open("./planets.txt")
if err != nil {
fmt.Println("Unable to open planet data:", err)
return
}
defer data.Close()
for {
_, err := fmt.Fscanf(
data,
"%s %d %d %s\n",
&name, &diam, &moons, &hasRing,
)
if err != nil {
if err == io.EOF {
break
} else {
fmt.Println("Scan error:", err)
return
}
}
fmt.Printf(
"%-10s %-10d %-6d %-6s\n",
name, diam, moons, hasRing,
)
}
}
8.3 缓冲IO总结
-
缓冲写入
:使用
bufio.NewWriter函数创建缓冲写入器。操作步骤为:创建io.Writer对象(如文件),使用bufio.NewWriter包装该对象,写入数据后调用Flush方法刷新缓冲区,示例代码如下:
func main() {
rows := []string{
"The quick brown fox",
"jumps over the lazy dog",
}
fout, err := os.Create("./filewrite.data")
writer := bufio.NewWriter(fout)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer fout.Close()
for _, row := range rows {
writer.WriteString(row)
}
writer.Flush()
}
-
缓冲读取
:使用
bufio.NewReader函数创建缓冲读取器。操作步骤为:打开io.Reader对象(如文件),使用bufio.NewReader包装该对象,使用读取方法(如ReadString)读取数据,示例代码如下:
func main() {
file, err := os.Open("./bufread0.go")
if err != nil {
fmt.Println("Unable to open file:", err)
return
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
break
} else {
fmt.Println("Error reading:", err)
return
}
}
fmt.Print(line)
}
}
9. 流程图总结
下面通过mermaid流程图展示不同IO操作的大致流程。
9.1 文件复制流程
graph TD;
A[开始] --> B[打开源文件];
B --> C{是否打开成功};
C -- 是 --> D[创建目标文件];
C -- 否 --> E[处理错误并退出];
D --> F{是否创建成功};
F -- 是 --> G[使用io.Copy复制数据];
F -- 否 --> E;
G --> H{复制是否成功};
H -- 是 --> I[关闭源文件和目标文件];
H -- 否 --> E;
I --> J[输出复制信息];
J --> K[结束];
9.2 缓冲写入流程
graph TD;
A[开始] --> B[创建文件];
B --> C{是否创建成功};
C -- 是 --> D[创建缓冲写入器];
C -- 否 --> E[处理错误并退出];
D --> F[写入数据到缓冲写入器];
F --> G[刷新缓冲写入器];
G --> H[关闭文件];
H --> I[结束];
10. 不同IO操作对比表格
| 操作类型 | 适用场景 | 优点 | 缺点 | 示例函数 |
|---|---|---|---|---|
| 无缓冲IO | 数据量小、对延迟不敏感的场景 | 代码简单直接 | 受底层操作系统IO请求延迟影响大 |
os.File
的
Read
和
Write
方法
|
| 缓冲IO | 数据量大、频繁读写的场景 | 减少底层IO请求次数,降低延迟 | 增加内存使用 |
bufio.NewReader
和
bufio.NewWriter
|
| 格式化IO | 需要对数据进行格式化输入输出的场景 | 可以方便地处理不同格式的数据 | 格式说明符使用不当可能导致错误 |
fmt.Fprintf
和
fmt.Fscanf
|
| 内存IO | 数据在内存中处理的场景 | 无需进行磁盘IO,速度快 | 受内存大小限制 |
bytes.Buffer
|
通过以上的总结、流程图和对比表格,我们可以更清晰地了解Go语言中不同数据IO操作的特点和适用场景,在实际开发中能够根据具体需求做出更合适的选择,高效地完成数据IO任务。
超级会员免费看
4万+

被折叠的 条评论
为什么被折叠?



