將整個文件讀取到內存是最基本的文件操作之一。這需要使用 ioutil]包中的 ReadFile 函數。
src
filehandling
filehandling.go
test.txt
package main import ( "fmt" "io/ioutil" ) func main() { data, err := ioutil.ReadFile("test.txt") if err != nil { fmt.Println("File reading error", err) return } fmt.Println("Contents of file:", string(data)) }
由於無法在 playground 上讀取文件,因此請在你的本地環境運行這個程序。
請在 test.txt 所在的位置運行該程序。
例如,對於 linux/mac,如果 test.txt 位於 /home/naveen/go/src/filehandling,可以使用下列步驟來運行程序。
$ cd /home/naveen/go/src/filehandling/
$ go install filehandling
$ workspacepath/bin/filehandling
> cd C:\Users\naveen.r\go\src\filehandling > go install filehandling > workspacepath\bin\filehandling.exe
該程序會輸出:
Contents of file: Hello World. Welcome to file handling in Go.
File reading error open test.txt: The system cannot find the file specified.
這是因為 Go 是編譯型語言。go install
有三種方法可以解決這個問題。
-
使用絕對文件路徑
-
使用命令行標記來傳遞文件路徑
-
將文件綁定在二進制文件中
讓我們來依次介紹。
package main import ( "fmt" "io/ioutil" ) func main() { data, err := ioutil.ReadFile("/home/naveen/go/src/filehandling/test.txt") if err != nil { fmt.Println("File reading error", err) return } fmt.Println("Contents of file:", string(data)) }
例如,可以在我的家目錄運行。
$ cd $HOME
$ go install filehandling
$ workspacepath/bin/filehandling
該程序打印出了 test.txt 的內容。
另一種解決方案是使用命令行標記來傳遞文件路徑。使用 flag
首先我們來看看 flag 包是如何工作的。flag 包有一個名為 String 的函數。該函數接收三個參數。第一個參數是標記名,第二個是默認值,第三個是標記的簡短描述。
讓我們來編寫程序,從命令行讀取文件名。將 filehandling.go 的內容替換如下:
package main import ( "flag" "fmt" ) func main() { fptr := flag.String("fpath", "test.txt", "file path to read from") flag.Parse() fmt.Println("value of fpath is", *fptr) }
在上述程序中第 8 行,通過 String 函數,創建了一個字符串標記,名稱是
在程序訪問 flag 之前,必須先調用 flag.Parse()。
在第 10 行,程序會打印出 flag 值。
使用下面命令運行程序。
wrkspacepath/bin/filehandling -fpath=/path-of-file/test.txt
我們傳入 /path-of-file/test.txt,賦值給了 fpath 標記。
該程序輸出:
value of fpath is /path-of-file/test.txt
這是因為 fpath 的默認值是 test.txt。
package main import ( "flag" "fmt" "io/ioutil" ) func main() { fptr := flag.String("fpath", "test.txt", "file path to read from") flag.Parse() data, err := ioutil.ReadFile(*fptr) if err != nil { fmt.Println("File reading error", err) return } fmt.Println("Contents of file:", string(data)) }
在上述程序里,命令行傳入文件路徑,程序讀取了該文件的內容。使用下面命令運行該程序。
wrkspacepath/bin/filehandling -fpath=/path-of-file/test.txt
Contents of file: Hello World. Welcome to file handling in Go.
有很多包可以幫助我們實現。我們會使用 packr,因為它很簡單,並且我在項目中使用它時,沒有出現任何問題。
第一步就是安裝 packr 包。
在命令提示符中輸入下面命令,安裝 packr 包。
go get -u github.com/gobuffalo/packr/...
packr 會把靜態文件(例如 .txt 文件)轉換為 .go 文件,接下來,.go 文件會直接嵌入到二進制文件中。packer 非常智能,在開發過程中,可以從磁盤而非二進制文件中獲取靜態文件。在開發過程中,當僅僅靜態文件變化時,可以不必重新編譯。
我們通過程序來更好地理解它。用以下內容來替換 handling.go 文件。
package main import ( "fmt" "github.com/gobuffalo/packr" ) func main() { box := packr.NewBox("../filehandling") data := box.String("test.txt") fmt.Println("Contents of file:", data) }
在上面程序的第 10 行,我們創建了一個新盒子(New Box)。盒子表示一個文件夾,其內容會嵌入到二進制中。在這里,我指定了 filehandling 文件夾,其內容包含 test.txt。在下一行,我們讀取了文件內容,並打印出來。
使用下面命令來運行程序。
go install filehandling
workspacepath/bin/filehandling
該程序會輸出:
Contents of file: Hello World. Welcome to file handling in Go.
你可以試着改變 test.txt 的內容,然后再運行 filehandling
現在我們來看看如何將 test.txt 打包到我們的二進制文件中。我們使用 packr 命令來實現。
運行下面的命令:
packr install -v filehandling
它會打印:
building box ../filehandling packing file filehandling.go packed file filehandling.go packing file test.txt packed file test.txt built box ../filehandling with ["filehandling.go" "test.txt"] filehandling
該命令將靜態文件綁定到了二進制文件中。
如果你不知道文件到底是由二進制還是磁盤來提供,我建議你刪除 test.txt,並在此運行 filehandling 命令。你將看到,程序打印出了 test.txt 的內容。太棒了:D。我們已經成功將靜態文件嵌入到了二進制文件中。
讓我們來編寫一個程序,以 3 個字節的塊為單位讀取 test.txt 文件。如下所示,替換 filehandling.go 的內容。
package main import ( "bufio" "flag" "fmt" "log" "os" ) func main() { fptr := flag.String("fpath", "test.txt", "file path to read from") flag.Parse() f, err := os.Open(*fptr) if err != nil { log.Fatal(err) } defer func() { if err = f.Close(); err != nil { log.Fatal(err) } }() r := bufio.NewReader(f) b := make([]byte, 3) for { _, err := r.Read(b) if err != nil { fmt.Println("Error reading file:", err) break } fmt.Println(string(b)) } }
在上述程序的第 15 行,我們使用命令行標記傳遞的路徑,打開文件。
在第 19 行,我們延遲了文件的關閉操作。
第 27 行的 Read 方法會讀取 len(b) 個字節(達到 3 字節),並返回所讀取的字節數。當到達文件最后時,它會返回一個 EOF 錯誤。程序的其他地方比較簡單,不做解釋。
如果我們使用下面命令來運行程序:
$ go install filehandling
$ wrkspacepath/bin/filehandling -fpath=/path-of-file/test.txt
會得到以下輸出:
Hel lo Wor ld. We lco me to fil e h and lin g i n G o. Error reading file: EOF
本節我們討論如何使用 Go 逐行讀取文件。這可以使用 bufio 來實現。
Hello World. Welcome to file handling in Go. This is the second line of the file. We have reached the end of the file.
逐行讀取文件涉及到以下步驟。
-
打開文件;
-
在文件上新建一個 scanner;
-
掃描文件並且逐行讀取。
將
package main import ( "bufio" "flag" "fmt" "log" "os" ) func main() { fptr := flag.String("fpath", "test.txt", "file path to read from") flag.Parse() f, err := os.Open(*fptr) if err != nil { log.Fatal(err) } defer func() { if err = f.Close(); err != nil { log.Fatal(err) } }() s := bufio.NewScanner(f) for s.Scan() { fmt.Println(s.Text()) } err = s.Err() if err != nil { log.Fatal(err) } }
在上述程序的第 15 行,我們用命令行標記傳入的路徑,打開文件。在第 24 行,我們用文件創建了一個新的 scanner。第 25 行的 Scan()
當 Scan 返回 false 時,除非已經到達文件末尾(此時 Err() 返回 nil),否則 Err() 就會返回掃描過程中出現的錯誤。
如果我使用下面命令來運行程序:
$ go install filehandling
$ workspacepath/bin/filehandling -fpath=/path-of-file/test.txt
程序會輸出:
Hello World. Welcome to file handling in Go. This is the second line of the file. We have reached the end of the file.