Go 系列教程 —— 36. 寫入文件 (大結局)


write files

歡迎來到 Golang 系列教程的第 36 篇。

在這一章我們將學習如何使用 Go 語言將數據寫到文件里面。並且還要學習如何同步的寫到文件里面。

這章教程包括如下幾個部分:

  • 將字符串寫入文件
  • 將字節寫入文件
  • 將數據一行一行的寫入文件
  • 追加到文件里
  • 並發寫文件

請在本地運行所有本教程的程序,因為 playground 對文件的操作支持的並不好。

將字符串寫入文件

最常見的寫文件就是將字符串寫入文件。這個寫起來非常的簡單。這個包含以下幾個階段。

  1. 創建文件
  2. 將字符串寫入文件

我們將得到如下代碼。

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 的文件。如果這個文件已經存在,那么 create 函數將截斷這個文件。該函數返回一個文件描述符

在第 14 行,我們使用 WriteString 將字符串 Hello World 寫入到文件里面。這個方法將返回相應寫入的字節數,如果有錯誤則返回錯誤。

最后,在第 21 行我們將文件關閉。

上面程序將打印:

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 } } 

在上面的程序中,第 15 行使用了 Write 方法將字節切片寫入到 bytes 這個文件里。這個文本在目錄 /home/naveen 里面。你也可以將這個目錄換成其他的目錄。剩余的程序自帶解釋。如果執行成功,這個程序將打印 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") } 

在上面程序的第 9 行,我們先創建一個名字叫做 lines 的文件。在第 17 行,我們用迭代並使用 for rang 循環這個數組,並使用 Fprintln Fprintln 函數 將 io.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 easylines 這個文件。

這個文件將以追加和寫的方式打開。這些標志將通過 Open 方法實現。當文件以追加的方式打開,我們添加新的行到文件里。

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 行,我們以寫的方式打開文件並將一行添加到文件里。當成功打開文件之后,在程序第 15 行,我們添加一行到文件里。程序成功將打印 file appended successfully。運行程序,新的行就加到文件里面去了。

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

並發寫文件

當多個 goroutines 同時(並發)寫文件時,我們會遇到競爭條件(race condition)。因此,當發生同步寫的時候需要一個 channel 作為一致寫入的條件。

我們將寫一個程序,該程序創建 100 個 goroutinues。每個 goroutinue 將並發產生一個隨機數,屆時將有 100 個隨機數產生。這些隨機數將被寫入到文件里面。我們將用下面的方法解決這個問題 .

  1. 創建一個 channel 用來讀和寫這個隨機數。
  2. 創建 100 個生產者 goroutine。每個 goroutine 將產生隨機數並將隨機數寫入到 channel 里。
  3. 創建一個消費者 goroutine 用來從 channel 讀取隨機數並將它寫入文件。這樣的話我們就只有一個 goroutinue 向文件中寫數據,從而避免競爭條件。
  4. 一旦完成則關閉文件。

我們開始寫產生隨機數的 produce 函數:

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

上面的方法產生隨機數並且將 data 寫入到 channel 中,之后通過調用 waitGroupDone 方法來通知任務已經完成。

讓我們看看將數據寫到文件的函數:

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 的文件。然后從 channel 中讀取隨機數並且寫到文件中。一旦讀取完成並且將隨機數寫入文件后,通過往 done 這個 cahnnel 中寫入 true 來通知任務已完成。

下面我們寫 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 函數在第 41 行創建寫入和讀取數據的 channel,在第 42 行創建 done 這個 channel,此 channel 用於消費者 goroutinue 完成任務之后通知 main 函數。第 43 行創建 Waitgroup 的實例 wg,用於等待所有生產隨機數的 goroutine 完成任務。

在第 44 行使用 for 循環創建 100 個 goroutines。在第 49 行調用 waitgroup 的 wait() 方法等待所有的 goroutines 完成隨機數的生成。然后關閉 channel。當 channel 關閉時,消費者 consume goroutine 已經將所有的隨機數寫入文件,在第 37 行 將 true 寫入 done 這個 channel 中,這個時候 main 函數解除阻塞並且打印 File written successfully

現在你可以用任何的文本編輯器打開文件 concurrent,可以看到 100 個隨機數已經寫入 :smile:

本教程到此結束。希望你能喜歡,祝你愉快。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM