Go 文件的讀取操作
os 包 和 bufio 包
Go 標準庫的 os 包,為我們提供很多操作文件的函數,如 Open(name) 打開文件、Create(name) 創建文件等函數,與之對應的是 bufio 包,os 包是直接對磁盤進行操作的,而 bufio 包則是帶有緩衝的操作,不用每次都去操作磁盤。
os.Open 與 os.OpenFile 以及 File.Read
-
Open(name string) (*File, error)通過 文件名 或 文件路徑+文件名 的形式打開一個文件,此文件只能用於讀操作,如果文件不存在則返回
PathError。- 參數
name為文件名或文件路徑+文件名。 - 返回值
*File為一個File結構體的指針類型,通過指針可以對文件進行讀寫等操作。 -
返回值
`error` 為打開文件的過程中產生的錯誤。
- 參數
-
OpenFile(name string, flag int, perm FileMode) (*File, error)通過指定 文件名 或 文件路徑+文件名、文件操作模式、文件權限三個參數打開一個文件,之後可對此文件進行讀寫操作。
- 參數
name為文件名或文件路徑+文件名。 - 參數
flag為指定文件操作模式,可選值有O_RDONLY→ 只讀操作、O_WRONLY→ 只寫操作、O_RDWR→ 讀寫操作、O_APPEND→ 寫入時向文件追加數據、O_CREATE→ 如果不存在,則創建一個新文件等。 - 參數
perm參數表示文件的模式和權限,例如0666為讀寫權限。如果對文件權限所對應的數字不瞭解,可以去學習一下。 - 返回值
*File為一個File結構體的指針類型,通過指針可以對文件進行讀寫等操作。 - 返回值
error為打開文件的過程中產生的錯誤。
- 參數
-
File.Read(b []byte) (n int, err error)讀取與
b等長度的字節,並存儲到b裏面。- 參數
b為一個切片數組,用於指定讀取長度和存儲字節數據。 - 返回值
n為所讀取字節的長度。 - 返回值
error為讀取字節的過程中產生的錯誤。
- 參數
讀取文件操作
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("1.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
data := make([]byte, 11)
count, err := file.Read(data)
if err != nil {
return
}
fmt.Println("字節數據:", data) // [72 101 108 108 111 32 119 111 114 108 100]
fmt.Println("字符串數據:", string(data)) // Hello world
fmt.Println("所獲取字節的長度:", count) // 11
}
執行結果:
字節數據: [72 101 108 108 111 32 119 111 114 108 100]
字符串數據: Hello world
所獲取字節的長度: 11
- 首先通過
Open函數打開1.txt文件,用file變量接收,默認為可讀模式; - 然後創建一個長度為
11的字節切片,接着通過file變量的方法Read讀取長度為11的字節數據。os.Open("1.txt")等價於os.OpenFile("1.txt", os.O_RDONLY, 0)。 - 最後打印讀取到的數據,文件操作完畢之後,需要關閉文件
file.Close()。
bufio.NewReader 和 Reader.ReadString
讀取文件,建議使用 bufio.NewReader 和 Reader.ReadString,減少磁盤的操作。
-
NewReader(rd io.Reader) *Reader
獲取一個有緩衝區的Reader指針變量,緩衝區默認大小為4096字節。通過變量可以對數據進行讀操作。- 參數
rd為一個接口,實現這個接口的數據類型變量都可以作為參數,例如上面提到的File。 - 返回值
*Reader為Reader結構體的指針,通過指針可以讀取緩衝區的數據。
- 參數
-
ReadString(delim byte) (string, error)
讀取數據,直到第一次遇到分隔符delim為止。讀取過程中發生錯誤會返回EOF錯誤信息。- 參數
delim為分隔符,每次讀取時遇到分隔符就會終止。 - 第一個返回值為所讀取的內容,內容包括分隔符。
- 第二個返回值為讀取過程中產生的錯誤信息。
- 參數
讀取文件操作
1.txt 文件的內容為:
Hello world
Hello Golang
Hello Gopher
import (
"bufio"
"fmt"
"io"
"os"
"strings"
)
func main() {
file, err := os.OpenFile("1.txt", os.O_RDONLY, 0)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
reader := bufio.NewReader(file)
for {
if lineData, err := reader.ReadString('\n'); err != nil {
if err == io.EOF {
// 因為是以換行符為分隔符,如果最後一行沒有換行符,那麼返回 io.EOF 錯誤時,也是可能讀到數據的,因此判斷一下是否讀到了數據
if len(lineData) > 0 {
fmt.Println(lineData)
}
break
}
} else {
fmt.Println(strings.TrimRight(lineData, "\n"))
}
}
}
執行結果:
Hello World
Hello Golang
Hello Gopher
- 首先通過
OpenFile函數打開1.txt文件,用file變量接收,指定為可讀模式; - 然後通過
NewReader函數創建一個緩衝區,將默認長度的字節讀取到緩衝區中; - 接着通過
Reader結構體的方法ReadString,以\n為分隔符,按行讀取數據,然後打印數據。 - 其中有一個注意點就是,因為是以換行符為分隔符,如果最後一行沒有換行符,那麼返回
io.EOF錯誤時,也是可能讀到數據的,因此需要判斷一下是否讀到了數據。
Go 文件的寫入操作
File.Write、File.WriteString、File.WriteAt
-
File.Write(b []byte) (n int, err error)直接操作磁盤往文件裏寫入數據,寫入單位為字節。
b參數:寫入的數據,類型為字節切片。- 返回值
n:寫入的字節數。 - 返回值
err:寫入數據的過程中產生的錯誤。
-
File.WriteString(s string) (n int, err error)直接操作磁盤往指定文件裏寫入數據,寫入單位為字符串。
s參數:寫入的字符串數據。- 返回值
n:寫入的字節數。 - 返回值
err:寫入數據的過程中產生的錯誤。
-
File.WriteAt(b []byte, off int64) (n int, err error)從指定位置
off往文件裏順序寫入數據,如果某個偏移量上有數據,則會覆蓋。b參數:寫入的數據,類型為字節切片。off參數:偏移量,從此位置開始寫入數據。- 返回值
n:寫入的字節數。 - 返回值
err:寫入數據的過程中產生的錯誤。
文件寫入操作
import (
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("test.txt", os.O_CREATE, 0)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
count, err := file.Write([]byte{'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\n'})
if err != nil {
return
}
fmt.Printf("寫入了 %d 字節\n", count)
count, err = file.WriteString("Hello Golang")
if err != nil {
return
}
fmt.Printf("寫入了長度為 %d 的字符串\n", count)
count, err = file.WriteAt([]byte{'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'}, 0)
if err != nil {
return
}
fmt.Printf("寫入了 %d 字節\n", count)
}
- 首先打開
test.txt文件,指定的模式為os.O_CREATE,如果文件不存在則會自動創建; - 然後通過
Write方法以字符的形式往文件裏寫入Hello World\n的內容; -
接着通過
WriteString方法以字符串的形式往文件裏寫入Hello Golang內容;此時文件裏的內容如下所示:Hello World Hello Golang -
最後通過
WriteAt方法,指定從偏移量為0的位置開始寫入數據xxxxxxxxxxx,由於0以及之後位置都有數據,因此原有數據被覆蓋了。最後文件的內容為:xxxxxxxxxxx Hello Golang
File.Seek
-
File.Seek(offset int64, whence int)相對於開頭位置或當前位置或末尾位置,將設置當前讀或寫的偏移量設置為
offset。offset參數:所要設置的偏移量。whence:相對於哪個位置開始設置偏移量的標誌,可選值為0→ 開頭位置,1→ 當前位置,2→ 末尾位置。
應用
import (
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("test.txt", os.O_CREATE, 0)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
_, err = file.WriteString("G0lang")
if err != nil {
return
}
_, err = file.Seek(1, 0)
if err != nil {
fmt.Println(err)
return
}
_, err = file.Write([]byte{'o'})
if err != nil {
fmt.Println(err)
return
}
}
- 打開
test.txt文件,指定的模式為os.O_CREATE,如果文件不存在則會自動創建; - 使用
WriteString方法往文件裏寫入G0lang字符串; - 此時發現第二個字符錯了,
0應該改成o;此時的偏移量是指向尾部的;使用Seek方法將偏移量移到第二個位置,然後寫入字符o,由於當前位置已有數據0,因此o將會覆蓋0;
bufio.NewWriter、Writer.WriteString、Writer.Flush
如果需要多次執行寫入文件的操作,推薦使用 bufio 裏的 Writer 結構體去操作,它會開闢一個緩衝區,默認大小為 4096 字節。在數據沒有被刷入磁盤之前,所寫入的數據都會暫時保存到緩衝區裏。
-
NewWriter(w io.Writer) *Writer開闢一個默認值為
4096字節的緩衝區,用於暫存寫入文件的數據內容,返回一個Writer結構體的指針變量w參數:類型為Writer接口,實現這個接口的數據類型變量都可以作為參數,例如File。- 返回值
*Writer:一個Writer結構體的指針變量,通過該變量可以往緩衝區裏寫入數據。
-
Writer.WriteString(s string) (int, error)往緩衝區寫入內容的方法。
- 參數
s為寫入的字符串。 - 第一個返回值為寫入的字節數。
- 第二個返回值為寫入數據的過程中產生的錯誤。
- 參數
-
Writer.Flush() error將所有的緩存數據寫入磁盤。
- 返回值為數據寫入磁盤的過程中產生的錯誤。
文件寫入操作
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("test.txt", os.O_CREATE, 0)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
writer := bufio.NewWriter(file)
_, err = writer.WriteString("Hello World\n")
if err != nil {
fmt.Println(err)
return
}
_, err = writer.WriteString("Hello Golang\n")
if err != nil {
fmt.Println(err)
return
}
_, err = writer.WriteString("Hello Gopher\n")
if err != nil {
fmt.Println(err)
return
}
writer.Flush()
}
- 首先打開
test.txt文件,指定的模式為os.O_CREATE,如果文件不存在則會自動創建; - 然後使用
NewWriter函數獲取一個Writer結構體的指針變量writer; - 接着通過
writer的WriteString方法將內容保存到緩衝區裏; - 最後調用
Flush方法,將所有的緩存數據寫入磁盤。
小結
文件的讀取操作推薦 bufio 包裏的 NewReader 函數和 Reader 結構體的方法 ReadString,能減少對磁盤的操作,高效讀取數據。
文件的寫入操作推薦 bufio.NewWriter、Writer.WriteString、Writer.Flush,使用它們代替 File 結構體裏的寫入方法,可以不用頻繁操作磁盤,提高寫入效率。
本文參與了SegmentFault 思否寫作挑戰賽活動,歡迎正在閲讀的你也加入。