go讀取文件


文件讀取是所有編程語言中最常見的操作之一。本教程我們會學習如何使用 Go 讀取文件。

本教程分為如下小節。

  • 將整個文件讀取到內存
    • 使用絕對文件路徑
    • 使用命令行標記來傳遞文件路徑
    • 將文件綁定在二進制文件中
  • 分塊讀取文件
  • 逐行讀取文件

將整個文件讀取到內存

將整個文件讀取到內存是最基本的文件操作之一。這需要使用 ioutil 包中的 ReadFile 函數。

讓我們在 Go 程序所在的目錄中,讀取一個文件。我已經在 GOPATH(譯注:原文是 GOROOT,應該是筆誤)中創建了文件夾,在該文件夾內部,有一個文本文件 test.txt,我們會使用 Go 程序 filehandling.go 來讀取它。test.txt 包含文本 “Hello World. Welcome to file handling in Go”。我的文件夾結構如下:

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 上讀取文件,因此請在你的本地環境運行這個程序。

在上述程序的第 9 行,程序會讀取文件,並返回一個字節切片,而這個切片保存在 data 中。在第 14 行,我們將 data 轉換為 string,顯示出文件的內容。

請在 test.txt 所在的位置運行該程序。

例如,對於 linux/mac,如果 test.txt 位於 /home/naveen/go/src/filehandling,可以使用下列步驟來運行程序。

$ cd /home/naveen/go/src/filehandling/ $ go install filehandling $ workspacepath/bin/filehandling 

對於 windows,如果 test.txt 位於 C:\Users\naveen.r\go\src\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. 

如果在其他位置運行這個程序(比如 /home/userdirectory),會打印下面的錯誤。

File reading error open test.txt: The system cannot find the file specified. 

這是因為 Go 是編譯型語言。go install 會根據源代碼創建一個二進制文件。二進制文件獨立於源代碼,可以在任何位置上運行。由於在運行二進制文件的位置上沒有找到 test.txt,因此程序會報錯,提示無法找到指定的文件。

有三種方法可以解決這個問題。

  1. 使用絕對文件路徑
  2. 使用命令行標記來傳遞文件路徑
  3. 將文件綁定在二進制文件中

讓我們來依次介紹。

1. 使用絕對文件路徑

要解決問題,最簡單的方法就是傳入絕對文件路徑。我已經修改了程序,把路徑改成了絕對路徑。

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

現在可以在任何位置上運行程序,打印出 test.txt 的內容。

例如,可以在我的家目錄運行。

$ cd $HOME $ go install filehandling $ workspacepath/bin/filehandling 

該程序打印出了 test.txt 的內容。

看似這是一個簡單的方法,但它的缺點是:文件必須放在程序指定的路徑中,否則就會出錯。

2. 使用命令行標記來傳遞文件路徑

另一種解決方案是使用命令行標記來傳遞文件路徑。使用 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 函數,創建了一個字符串標記,名稱是 fpath,默認值是 test.txt,描述為 file path to read from。這個函數返回存儲 flag 值的字符串變量的地址。

在程序訪問 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 

請將 /path-of-file/ 替換為 test.txt 的真實路徑。該程序將打印:

Contents of file: Hello World. Welcome to file handling in Go.

3. 將文件綁定在二進制文件中

雖然從命令行獲取文件路徑的方法很好,但還有一種更好的解決方法。如果我們能夠將文本文件捆綁在二進制文件,豈不是很棒?這就是我們下面要做的事情。

有很多可以幫助我們實現。我們會使用 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 命令來運行程序。程序可以正常運行。packr 非常智能,在開發階段可以從磁盤加載文件。

使用下面命令來運行程序。

go install filehandling workspacepath/bin/filehandling 

該命令可以在其他位置運行。packr 很聰明,可以獲取傳遞給 NewBox 命令的目錄的絕對路徑。

該程序會輸出:

Contents of file: Hello World. Welcome to file handling in Go.

你可以試着改變 test.txt 的內容,然后再運行 filehandling。可以看到,無需再次編譯,程序打印出了 test.txt 的更新內容。完美!:smile:

現在我們來看看如何將 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 

該命令將靜態文件綁定到了二進制文件中。

在運行上述命令之后,使用命令 workspacepath/bin/filehandling 來運行程序。程序會打印出 test.txt 的內容。於是從二進制文件中,我們讀取了 test.txt 的內容。

如果你不知道文件到底是由二進制還是磁盤來提供,我建議你刪除 test.txt,並在此運行 filehandling 命令。你將看到,程序打印出了 test.txt 的內容。太棒了:D。我們已經成功將靜態文件嵌入到了二進制文件中。

分塊讀取文件

在前面的章節,我們學習了如何把整個文件讀取到內存。當文件非常大時,尤其在 RAM 存儲量不足的情況下,把整個文件都讀入內存是沒有意義的。更好的方法是分塊讀取文件。這可以使用 bufio 包來完成。

讓我們來編寫一個程序,以 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 行,我們延遲了文件的關閉操作。

在上面程序的第 24 行,我們新建了一個緩沖讀取器(buffered reader)。在下一行,我們創建了長度和容量為 3 的字節切片,程序會把文件的字節讀取到切片中。

第 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 來實現。

請將 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.

逐行讀取文件涉及到以下步驟。

  1. 打開文件;
  2. 在文件上新建一個 scanner;
  3. 掃描文件並且逐行讀取。

將 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) } }() 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() 方法讀取文件的下一行,如果可以讀取,就可以使用 Text() 方法。

當 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. 

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

 

https://studygolang.com/articles/14669


免責聲明!

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



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