超全總結:Go 讀文件的 10 種方法
大家好,我是明哥。
Go 中對文件內容讀寫的方法,非常地多,其中大多數是基于 syscall 或者 os 庫的高級封裝,不同的庫,適用的場景又不太一樣,為免新手在這塊上裁跟頭,我花了點時間把這些內容梳理了下。
這篇是上篇,先介紹讀取文件的 10 種方法,過兩天再介紹寫入文件的。
1. 整個文件讀取入內存
直接將數據直接讀取入內存,是效率最高的一種方式,但此種方式,僅適用于小文件,對于大文件,則不適合,因為比較浪費內存。
1.1 直接指定文件名讀取
有兩種方法
第一種:使用 os.ReadFile
- package main
- import (
- "fmt"
- "os"
- )
- func main() {
- content, err := os.ReadFile("a.txt")
- if err != nil {
- panic(err)
- }
- fmt.Println(string(content))
- }
第二種:使用 ioutil.ReadFile
- package main
- import (
- "io/ioutil"
- "fmt"
- )
- func main() {
- content, err := ioutil.ReadFile("a.txt")
- if err != nil {
- panic(err)
- }
- fmt.Println(string(content))
其實在 Go 1.16 開始,ioutil.ReadFile 就等價于 os.ReadFile,二者是完全一致的
- // ReadFile reads the file named by filename and returns the contents.
- // A successful call returns err == nil, not err == EOF. Because ReadFile
- // reads the whole file, it does not treat an EOF from Read as an error
- // to be reported.
- //
- // As of Go 1.16, this function simply calls os.ReadFile.
- func ReadFile(filename string) ([]byte, error) {
- return os.ReadFile(filename)
- }
1.2 先創(chuàng)建句柄再讀取
如果僅是讀取,可以使用高級函數 os.Open
- package main
- import (
- "os"
- "io/ioutil"
- "fmt"
- )
- func main() {
- file, err := os.Open("a.txt")
- if err != nil {
- panic(err)
- }
- defer file.Close()
- content, err := ioutil.ReadAll(file)
- fmt.Println(string(content))
之所以說它是高級函數,是因為它是只讀模式的 os.OpenFile
- // Open opens the named file for reading. If successful, methods on
- // the returned file can be used for reading; the associated file
- // descriptor has mode O_RDONLY.
- // If there is an error, it will be of type *PathError.
- func Open(name string) (*File, error) {
- return OpenFile(name, O_RDONLY, 0)
- }
因此,你也可以直接使用 os.OpenFile,只是要多加兩個參數
- package main
- import (
- "fmt"
- "io/ioutil"
- "os"
- )
- func main() {
- file, err := os.OpenFile("a.txt", os.O_RDONLY, 0)
- if err != nil {
- panic(err)
- }
- defer file.Close()
- content, err := ioutil.ReadAll(file)
- fmt.Println(string(content))
- }
2. 每次只讀取一行
一次性讀取所有的數據,太耗費內存,因此可以指定每次只讀取一行數據。方法有三種:
- bufio.ReadLine()
- bufio.ReadBytes('\n')
- bufio.ReadString('\n')
在 bufio 的源碼注釋中,曾說道 bufio.ReadLine() 是低級庫,不太適合普通用戶使用,更推薦用戶使用 bufio.ReadBytes 和 bufio.ReadString 去讀取單行數據。
因此,這里不再介紹 bufio.ReadLine()
2.1 使用 bufio.ReadBytes
- package main
- import (
- "bufio"
- "fmt"
- "io"
- "os"
- "strings"
- )
- func main() {
- // 創(chuàng)建句柄
- fi, err := os.Open("christmas_apple.py")
- if err != nil {
- panic(err)
- }
- // 創(chuàng)建 Reader
- r := bufio.NewReader(fi)
- for {
- lineBytes, err := r.ReadBytes('\n')
- line := strings.TrimSpace(string(lineBytes))
- if err != nil && err != io.EOF {
- panic(err)
- }
- if err == io.EOF {
- break
- }
- fmt.Println(line)
- }
- }
2.2 使用 bufio.ReadString
- package main
- import (
- "bufio"
- "fmt"
- "io"
- "os"
- "strings"
- )
- func main() {
- // 創(chuàng)建句柄
- fi, err := os.Open("a.txt")
- if err != nil {
- panic(err)
- }
- // 創(chuàng)建 Reader
- r := bufio.NewReader(fi)
- for {
- line, err := r.ReadString('\n')
- line = strings.TrimSpace(line)
- if err != nil && err != io.EOF {
- panic(err)
- }
- if err == io.EOF {
- break
- }
- fmt.Println(line)
- }
- }
3. 每次只讀取固定字節(jié)數
每次僅讀取一行數據,可以解決內存占用過大的問題,但要注意的是,并不是所有的文件都有換行符 \n。
因此對于一些不換行的大文件來說,還得再想想其他辦法。
3.1 使用 os 庫
通用的做法是:
- 先創(chuàng)建一個文件句柄,可以使用 os.Open 或者 os.OpenFile
- 然后 bufio.NewReader 創(chuàng)建一個 Reader
- 然后在 for 循環(huán)里調用 Reader 的 Read 函數,每次僅讀取固定字節(jié)數量的數據。
- package main
- import (
- "bufio"
- "fmt"
- "io"
- "os"
- )
- func main() {
- // 創(chuàng)建句柄
- fi, err := os.Open("a.txt")
- if err != nil {
- panic(err)
- }
- // 創(chuàng)建 Reader
- r := bufio.NewReader(fi)
- // 每次讀取 1024 個字節(jié)
- buf := make([]byte, 1024)
- for {
- n, err := r.Read(buf)
- if err != nil && err != io.EOF {
- panic(err)
- }
- if n == 0 {
- break
- }
- fmt.Println(string(buf[:n]))
- }
- }
3.2 使用 syscall 庫
os 庫本質上也是調用 syscall 庫,但由于 syscall 過于底層,如非特殊需要,一般不會使用 syscall
本篇為了內容的完整度,這里也使用 syscall 來舉個例子。
本例中,會每次讀取 100 字節(jié)的數據,并發(fā)送到通道中,由另外一個協(xié)程進行讀取并打印出來。
- package main
- import (
- "fmt"
- "sync"
- "syscall"
- )
- func main() {
- fd, err := syscall.Open("christmas_apple.py", syscall.O_RDONLY, 0)
- if err != nil {
- fmt.Println("Failed on open: ", err)
- }
- defer syscall.Close(fd)
- var wg sync.WaitGroup
- wg.Add(2)
- dataChan := make(chan []byte)
- go func() {
- wg.Done()
- for {
- data := make([]byte, 100)
- n, _ := syscall.Read(fd, data)
- if n == 0 {
- break
- }
- dataChan <- data
- }
- close(dataChan)
- }()
- go func() {
- defer wg.Done()
- for {
- select {
- case data, ok := <-dataChan:
- if !ok {
- return
- }
- fmt.Printf(string(data))
- default:
- }
- }
- }()
- wg.Wait()
- }