写入文件
Q7nl1s admin

在本教程中,我们将学习如何使用Go向文件写入数据。我们还将学习如何同时向一个文件写入数据。

本教程有以下几个部分

  • 向文件写入字符串
  • 向文件写入字节
  • 逐行向文件中写入数据
  • 向文件追加数据
  • 同时向一个文件写入数据

请在你的本地系统中运行本教程的所有程序,因为playground不支持文件操作。

向文件写入字符串

最常见的文件写入操作之一是向文件写入字符串。这是很简单的操作。它包括以下几个步骤。

  1. 创建文件
  2. 向文件中写入字符串

让我们马上进入到代码中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"os"
)

func main() {
f, err := os.Create("test.txt")
if err != nil {
fmt.Println(err)
return
}
l, err := f.WriteString("Hello World")
if err != nil {
fmt.Println(err)
f.Close()
return
}
fmt.Println(l, "bytes written successfully")
err = f.Close()
if err != nil {
fmt.Println(err)
return
}
}

上面程序第9行中的create函数创建了一个名为test.txt的文件。如果这个名字的文件已经存在,那么创建函数就会截断该文件。这个函数返回一个文件描述符(File descriptor)。

在第14行,我们使用WriteString方法将字符串Hello World写到文件中。这个方法返回写入的字节数和错误(如果有)。

最后,我们在第21行关闭该文件。

上述程序将打印

1
11 bytes written successfully  

你可以在执行该程序的目录中找到一个名为test.txt的文件。如果你用任何文本编辑器打开该文件,你会发现它包含了Hello World的文字。

向文件写入字节

向文件写入字节与向文件写入字符串非常相似。我们将使用Write方法把字节写到文件中。下面的程序将一个字节片写到一个文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"os"
)

func main() {
f, err := os.Create("/home/naveen/bytes")
if err != nil {
fmt.Println(err)
return
}
d2 := []byte{104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100}
n2, err := f.Write(d2)
if err != nil {
fmt.Println(err)
f.Close()
return
}
fmt.Println(n2, "bytes written successfully")
err = f.Close()
if err != nil {
fmt.Println(err)
return
}
}

在上面的程序中,在第15行,我们使用Write方法将一个字节片写到/home/naveen目录下名为bytes的文件中。你可以把这个目录换成另一个。剩下的程序是不言自明的。这个程序将打印11 bytes written successfully,它将创建一个名为bytes的文件。打开该文件,你可以看到它包含文本hello bytes

逐行向文件写入字符串

另一个常见的文件操作是需要逐行向文件写入字符串。在本节中,我们将编写一个程序来创建一个具有以下内容的文件。

1
2
3
Welcome to the world of Go.  
Go is a compiled language.
It is easy to learn Go.

让我们马上开始写代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"os"
)

func main() {
f, err := os.Create("lines")
if err != nil {
fmt.Println(err)
f.Close()
return
}
d := []string{"Welcome to the world of Go1.", "Go is a compiled language.", "It is easy to learn Go."}

for _, v := range d {
fmt.Fprintln(f, v)
if err != nil {
fmt.Println(err)
return
}
}
err = f.Close()
if err != nil {
fmt.Println(err)
return
}
fmt.Println("file written successfully")
}

在上述程序的第9行,我们创建一个名为lines的新文件。在第17行,我们使用for range循环遍历数组,并使用Fprintln函数将这些行写到文件中。Fprintln函数以io.writer为参数,附加一个新行,这正是我们想要的。运行这个程序将打印file written successfully,并在当前目录下创建一个文件lines。文件lines的内容在下面提供。

1
2
3
Welcome to the world of Go1.  
Go is a compiled language.
It is easy to learn Go.

追加到一个文件

在这一节中,我们将在上一节中创建的lines文件中再添加一行。我们将在lines文件中添加 “File handling is easy“这一行。

该文件必须以追加和只写模式打开。这些标志是传递给Open函数的参数。在文件以追加模式打开后,我们将新的行添加到文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"os"
)

func main() {
f, err := os.OpenFile("lines", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Println(err)
return
}
newLine := "File handling is easy."
_, err = fmt.Fprintln(f, newLine)
if err != nil {
fmt.Println(err)
f.Close()
return
}
err = f.Close()
if err != nil {
fmt.Println(err)
return
}
fmt.Println("file appended successfully")
}

在上述程序的第9行中,我们打开了一个新的文件。在上面的程序第9行,我们以追加和只写模式打开文件。在文件被成功打开后,我们在第15行向文件添加新行。这个程序将打印file appended successfull。运行这个程序后,文件中的行内容将是:

1
2
3
4
Welcome to the world of Go1.  
Go is a compiled language.
It is easy to learn Go.
File handling is easy.

同时向文件写入

当多个goroutine同时向一个文件写入时,我们会出现一个竞争条件。因此,对一个文件的并发写入应该用一个通道来协调。

我们将写一个程序,创建100个goroutine。每个goroutine将同时产生一个随机数,因此总共产生了100个随机数。这些随机数将被写入一个文件中。我们将用以下方法来解决这个问题。

  1. 创建一个通道,用来读写生成的随机数。
  2. 创建100个producer goroutine。每个goroutine将生成一个随机数,并将随机数写到一个通道上。
  3. 创建一个consumer goroutine,它将从通道中读取并将生成的随机数写到文件中。因此,我们只有一个goroutine同时向一个文件写入,从而避免了竞争条件:)
  4. 完成后关闭文件。

让我们先写一下生成随机数的produce函数。

1
2
3
4
5
func produce(data chan int, wg *sync.WaitGroup) {  
n := rand.Intn(999)
data <- n
wg.Done()
}

上面的函数生成了一个随机数并将其写入data通道,然后在waitgroup上调用Done来通知它已经完成了任务。

现在让我们转到向文件写入的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func consume(data chan int, done chan bool) {  
f, err := os.Create("concurrent")
if err != nil {
fmt.Println(err)
return
}
for d := range data {
_, err = fmt.Fprintln(f, d)
if err != nil {
fmt.Println(err)
f.Close()
done <- false
return
}
}
err = f.Close()
if err != nil {
fmt.Println(err)
done <- false
return
}
done <- true
}

consume函数创建了一个名为concurrent的文件。然后,它从data通道中读取随机数并写入该文件。一旦它读完和写完所有的随机数,它就把 true写到done通道,以通知它已经完成了它的任务。

让我们来编写main函数,完成这个程序。我在下面提供了整个程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package main

import (
"fmt"
"math/rand"
"os"
"sync"
)

func produce(data chan int, wg *sync.WaitGroup) {
n := rand.Intn(999)
data <- n
wg.Done()
}

func consume(data chan int, done chan bool) {
f, err := os.Create("concurrent")
if err != nil {
fmt.Println(err)
return
}
for d := range data {
_, err = fmt.Fprintln(f, d)
if err != nil {
fmt.Println(err)
f.Close()
done <- false
return
}
}
err = f.Close()
if err != nil {
fmt.Println(err)
done <- false
return
}
done <- true
}

func main() {
data := make(chan int)
done := make(chan bool)
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go produce(data, &wg)
}
go consume(data, done)
go func() {
wg.Wait()
close(data)
}()
d := <-done
if d == true {
fmt.Println("File written successfully")
} else {
fmt.Println("File writing failed")
}
}

main函数在第41行创建了data通道,从该通道中读取和写入随机数。第42行的done通道被consume goroutine用来通知main它已经完成了任务。43行的wg waitgroup用于等待所有100个goroutine完成随机数的生成。

第44行的for循环用于创建100个goroutine。第49行中的goroutine调用了wait(),等待所有100个goroutine完成随机数的生成。在这之后,它关闭了通道。一旦通道被关闭,并且consume goroutine已经完成了将所有生成的随机数写入文件,它在第37行向done的通道写入true。main goroutine就解除了封锁,并打印File written successfully

现在你可以在任何文本编辑器中打开该文件,看到100个生成的随机数:)

这样我们就到了本教程的结尾。希望你喜欢阅读。祝你有一个愉快的一天。

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View