写文件
在这篇教程中,我们将学到使用Go向文件中写入数据。我们还将学到如何并发写入一个文件。
文章包含以下几个部分:
. 向文件中写入字符串
. 向文件中写入字节
. 向文件中追加内容
. 并发写入文件
向文件中写入字符串
最常见的写操作就是向文件中写入字符串。要做到这一点,很简单。我们只需要遵循如下几个步骤即可:
-
创建文件
-
向文件中写入字符串
我们直接上代码吧。
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
}
}
上面程序中的create()函数,创建了一个名为test.txt的文件。如果这个名字的文件已经存在了,那么此函数就会清空此文件。create()函数会返回一个文件描述符。
l, err := f.WriteString("Hello World") 在这行代码中,我们使用WriteString方法把字符串“Hello World”写入到文件中。该方法会返回写入的字节数以及出现的错误。
err = f.Close()在这里我们关闭了文件。程序的输出如下:
11 bytes written successfully
这时候,你会发现在目录下创建了一个名为test.txt的文件。如果你使用文本编辑器打开此文件的话,你就会发现文本中已经包含了“Hello World”字符串了。
向文件中写入字节
向文件中写入字节和写入字符串的操作类似,我们可以使用Write方法向文件中写入字节。下面的这个程序会把一个切片的字节写入到文件中。
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
}
}
n2, err := f.Write(d2)在这行代码中,我们使用Write方法把一个切片的字节写入到名为bytes的文件中。剩下的程序都比较容易理解。
程序将打印11 bytes written successfully。同时创建了一个名为bytes的文件。打开这个文件,里面就已经包含了“hello bytes”。
一行行地向文件中写入字符串
除了上面这些操作之外,另一个常用的文件操作就是一行行地向文件中写入字符串。在这部分中,我们会创建一个文件,并把下面这些内容写入到里面:
Welcome to the world of Go.
Go is a compiled language.
It is easy to learn Go.
我们来写段代码吧。
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")
}
f, err := os.Create("lines")这行代码创建了一个名为lines的文件。for _, v := range d 这样代码,我们使用for循环来遍历一个数组,并使用Fprintln函数把这些行写入到文件中。Fprintln函数接收了一个Writer作为参数,并把一行字符串追加到文件中。和我们预期的一样,运行这个程序的话,会打印出file written successfully。同时在目录下会新建一个名为lines的文件。lines文件的内容如下:
Welcome to the world of Go1.
Go is a compiled language.
It is easy to learn Go.
向文件中追加内容
在这部分,我们会向lines文件追加一行内容。我们会把"File handling is easy"追加到lines文件中。
在这种模式下,文件必须处在Append and Write only mode模式下。这些标志会作为参数传递给Open函数。
文件以append模式打开之后,我们就可以向文件中添加一行代码。
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")
}
在上面的程序中,我们在append and write only mode模式下,打开文件。成功打开文件之后,我们向文件中写入一行数据。
程序输出"file appended successfully"。运行此程序之后,lines文件中的内容为:
Welcome to the world of Go1.
Go is a compiled language.
It is easy to learn Go.
File handling is easy.
并发写入文件
当有多个goroutine并发地向一个文件写入数据时,会发生资源竞争。因此,并发写入同一个文件,需要使用channel进行协调。
我们会写一个程序,这个程序会创建100个goroutine。每个goroutine都将产生一个并发的随机数,所以总共会产生100个随机数。
这些随机数会被写入到文件中,我们将使用到下面这些方法来解决这个问题。
-
创建一个channel,我们将使用这个channel来把生成的随机数写入到文件中。
-
创建100个生产者goroutine,其中每一个goroutine都将产生了一个随机数,并把这个随机数写入到channel中。
-
创建一个消费者goroutine,这个goroutine会从channel中读取,并把生成的随机数写入到文件中。因此,我们只有一个goroutine向文件中写入随机数。
因此,也就避免了资源竞争的问题。 -
完成之后,关闭文件
我们先来写一个produce函数,由这个produce函数生成随机数。
func produce(data chan int, wg *sync.WaitGroup) {
n := rand.Intn(999)
data <- n
wg.Done()
}
上面的这个函数会生成一个随机数,并把随机数写入到通道data中,之后调用了waitgroup的Done()方法,用以提醒waitgroup,此项任务
已完成。
我们再来看一下,把随机数写入到文件的函数:
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方法,来完成这个程序。
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函数创建了一个data通道,程序会从data通道中读取随机数,然后写入到文件中。
当任务完成之后,消费者goroutine会使用done通道来通知main函数。wg等待组会等待所有的100个goroutine完成随机数的生成。
在第44行的for循环中,我们创建了100个goroutine。我们在waitgroup中调用wait方法,等待所有的100个goroutine都已完成随机数的生成。完成之后,就把通道关闭。当通道关闭的时候,消费者goroutine已经把所有生成的随机数都写入到了文件中。
它把true写入到done通道中,main函数的goroutine收到通知之后,取消阻塞,并打印出: File written successfully。
此时,你打开concurrent文件,就会看到里面有100个随机数。
致谢
感谢您百忙之中阅读本文。如果有任何反馈和问题,请您在评论区留言。
备注
本文系翻译之作原文博客地址。
网友评论