超全總結(jié):Go 讀文件的 10 種方法
大家好,我是明哥。
Go 中對(duì)文件內(nèi)容讀寫的方法,非常地多,其中大多數(shù)是基于 syscall 或者 os 庫的高級(jí)封裝,不同的庫,適用的場景又不太一樣,為免新手在這塊上裁跟頭,我花了點(diǎn)時(shí)間把這些內(nèi)容梳理了下。
這篇是上篇,先介紹讀取文件的 10 種方法,過兩天再介紹寫入文件的。
1. 整個(gè)文件讀取入內(nèi)存
直接將數(shù)據(jù)直接讀取入內(nèi)存,是效率最高的一種方式,但此種方式,僅適用于小文件,對(duì)于大文件,則不適合,因?yàn)楸容^浪費(fèi)內(nèi)存。
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))
其實(shí)在 Go 1.16 開始,ioutil.ReadFile 就等價(jià)于 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)建句柄再讀取
如果僅是讀取,可以使用高級(jí)函數(shù) 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))
之所以說它是高級(jí)函數(shù),是因?yàn)樗侵蛔x模式的 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,只是要多加兩個(gè)參數(shù)
- 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. 每次只讀取一行
一次性讀取所有的數(shù)據(jù),太耗費(fèi)內(nèi)存,因此可以指定每次只讀取一行數(shù)據(jù)。方法有三種:
- bufio.ReadLine()
- bufio.ReadBytes('\n')
- bufio.ReadString('\n')
在 bufio 的源碼注釋中,曾說道 bufio.ReadLine() 是低級(jí)庫,不太適合普通用戶使用,更推薦用戶使用 bufio.ReadBytes 和 bufio.ReadString 去讀取單行數(shù)據(jù)。
因此,這里不再介紹 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é)數(shù)
每次僅讀取一行數(shù)據(jù),可以解決內(nèi)存占用過大的問題,但要注意的是,并不是所有的文件都有換行符 \n。
因此對(duì)于一些不換行的大文件來說,還得再想想其他辦法。
3.1 使用 os 庫
通用的做法是:
- 先創(chuàng)建一個(gè)文件句柄,可以使用 os.Open 或者 os.OpenFile
- 然后 bufio.NewReader 創(chuàng)建一個(gè) Reader
- 然后在 for 循環(huán)里調(diào)用 Reader 的 Read 函數(shù),每次僅讀取固定字節(jié)數(shù)量的數(shù)據(jù)。
- 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 個(gè)字節(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 庫本質(zhì)上也是調(diào)用 syscall 庫,但由于 syscall 過于底層,如非特殊需要,一般不會(huì)使用 syscall
本篇為了內(nèi)容的完整度,這里也使用 syscall 來舉個(gè)例子。
本例中,會(huì)每次讀取 100 字節(jié)的數(shù)據(jù),并發(fā)送到通道中,由另外一個(gè)協(xié)程進(jìn)行讀取并打印出來。
- 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()
- }