golang教程之讀取檔案
文章目錄
讀取檔案
將整個檔案讀入記憶體
最基本的檔案操作之一是將整個檔案讀入記憶體。 這是在ioutil
包的ReadFile
函式的幫助下完成的。
讓我們從go程式所在的目錄中讀取一個檔案。 我已經在我的GOROOT
filehandling
,我在裡面有一個文字檔案test.txt
,它將使用我們的Go程式filehandling.go
來讀取。 test.txt
包含文字“Hello World,歡迎使用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行中讀取檔案並返回儲存在資料中的位元組片。我們將資料轉換為字串並顯示檔案的內容。
請從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
,則使用以下步驟執行此程式,
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是一種編譯語言。 安裝的作用是,它從原始碼建立二進位制檔案。 二進位制檔案獨立於原始碼,可以從任何位置執行。 由於在執行二進位制檔案的位置找不到test.txt,程式會報錯它無法找到指定的檔案。
有三種方法可以解決這個問題,
- 使用絕對檔案路徑
- 將檔案路徑作為命令列標誌傳遞
- 將文字檔案與二進位制檔案捆綁在一起
讓我們一個一個討論。
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
函式。 此函式接受3個引數。 第一個是標誌的名稱,第二個是預設值,第三個是標誌的簡短描述。
讓我們編寫一個小程式來從命令列中讀取檔名。 用以下內容替換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行中,建立一個名為fpath
的字串標誌,其預設值為test.txt
,並使用String
函式讀取描述檔案路徑。 此函式返回儲存標誌值的字串變數的地址。
應該在程式訪問任何標誌之前呼叫flag.Parse()
。
我們在第一行列印標誌的值。
使用該命令執行此程式時
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
,它將列印
value of fpath is test.txt
因為test.txt是fpath的預設值。
現在我們知道如何從命令列讀取檔案路徑,讓我們繼續完成我們的檔案讀取程式。
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
包。
在命令提示符中鍵入以下命令以安裝該程式包
go get -u github.com/gobuffalo/packr/...
packr
將靜態檔案(如.txt)轉換為.go檔案,然後直接嵌入到二進位制檔案中。 Packer
非常智慧,可以在開發過程中從磁碟而不是從二進位制檔案中獲取靜態檔案。 這樣可以防止在只有靜態檔案發生變化時需要在開發期間重新編譯。
一個程式將使我們更好地理解事物。 用以下內容替換filehandling.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行程式,我們正在建立一個新盒子。盒子表示一個資料夾,其內容將嵌入到二進位制檔案中。 在這種情況下,我指定包含test.txt
的filehandling
資料夾。 在下一行中,我們讀取檔案的內容並打印出來。
當我們處於開發階段時,我們可以使用go install
命令來執行該程式。 它將按預期工作。 packr
足夠智慧,可以在開發階段從磁碟載入檔案。
使用以下命令執行該程式。
go install filehandling
workspacepath/bin/filehandling
這些命令可以從任何位置執行。 Packr
足夠智慧,可以獲取傳遞給NewBox命令的目錄的絕對路徑。
這個程式將打印出來
Contents of file: Hello World. Welcome to file handling in Go.
嘗試更改test.txt
的內容並再次執行檔案處理。 您可以看到該程式列印test.txt
的更新內容,而無需任何重新編譯。完美。
現在讓我們將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
並再次執行命令檔案控制代碼。 您可以看到test.txt的內容已打印出來。 太棒了,我們已成功將靜態檔案嵌入到二進位制檔案中。
以小塊讀取檔案
在上一節中,我們學習瞭如何將整個檔案載入到記憶體中。 當檔案的大小非常大時,將整個檔案讀入記憶體是沒有意義的,尤其是在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))
}
}
在上面的程式中,我們使用從命令列標誌傳遞的路徑開啟檔案。
在第19行我們延遲關閉檔案。
上面的程式第24行中建立了一個新的緩衝讀取器。 在下一行中,我們建立一個長度和容量為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.
以下是逐行讀取檔案所涉及的步驟。
- 開啟檔案
- 從檔案中建立一個新的掃描程式
- 掃描檔案並逐行讀取。
用以下內容替換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)
}
}
在上面的程式中,我們使用從命令列標誌傳遞的路徑開啟檔案。在第24行我們使用該檔案建立一個新的掃描器。第25行為scan()
的方法。通過text()
方法可讀取檔案的下一行。
在Scan
返回false之後,Err()
方法將返回掃描期間發生的任何錯誤,除非檔案讀取結束,Err()
將返回nil
。
如果我們使用命令執行上面的程式,
$] 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.