歡迎來到 Golang 系列教程的第 36 篇。
在這一章我們將學習如何使用 Go 語言將數據寫到文件里面。並且還要學習如何同步的寫到文件里面。
這章教程包括如下幾個部分:
- 將字符串寫入文件
- 將字節寫入文件
- 將數據一行一行的寫入文件
- 追加到文件里
- 並發寫文件
請在本地運行所有本教程的程序,因為 playground 對文件的操作支持的並不好。
將字符串寫入文件
最常見的寫文件就是將字符串寫入文件。這個寫起來非常的簡單。這個包含以下幾個階段。
- 創建文件
- 將字符串寫入文件
我們將得到如下代碼。
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 easy 到 lines
這個文件。
這個文件將以追加和寫的方式打開。這些標志將通過 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 個隨機數產生。這些隨機數將被寫入到文件里面。我們將用下面的方法解決這個問題 .
- 創建一個 channel 用來讀和寫這個隨機數。
- 創建 100 個生產者 goroutine。每個 goroutine 將產生隨機數並將隨機數寫入到 channel 里。
- 創建一個消費者 goroutine 用來從 channel 讀取隨機數並將它寫入文件。這樣的話我們就只有一個 goroutinue 向文件中寫數據,從而避免競爭條件。
- 一旦完成則關閉文件。
我們開始寫產生隨機數的 produce
函數:
func produce(data chan int, wg *sync.WaitGroup) { n := rand.Intn(999) data <- n wg.Done() }
上面的方法產生隨機數並且將 data
寫入到 channel 中,之后通過調用 waitGroup
的 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 }
這個 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 個隨機數已經寫入
本教程到此結束。希望你能喜歡,祝你愉快。