查看本系列教程目錄,請點擊 零基礎小白入門 Go語言 系列教程
Go 中對文件內容讀寫的方法,非常地多,其中大多數是基於 os 庫的高級封裝,不同的庫,適用的場景又不太一樣,為免新手在這塊上裁跟頭,我花了點時間把這些內容梳理了下。
這篇是上篇,先介紹讀取文件的 9 種方法,過兩天再介紹寫文件的。
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 先創建句柄再讀取
如果僅是讀取,可以使用高級函數 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() {
// 創建句柄
fi, err := os.Open("christmas_apple.py")
if err != nil {
panic(err)
}
// 創建 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() {
// 創建句柄
fi, err := os.Open("a.txt")
if err != nil {
panic(err)
}
// 創建 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. 每次只讀取固定字節數
每次僅讀取一行數據,可以解決內存占用過大的問題,但要注意的是,並不是所有的文件都有換行符 \n
。
因此對於一些不換行的大文件來說,還得再想想其他辦法。
3.1 使用 os 庫
通用的做法是:
- 先創建一個文件句柄,可以使用 os.Open 或者 os.OpenFile
- 然后 bufio.NewReader 創建一個 Reader
- 然后在 for 循環里調用 Reader 的 Read 函數,每次僅讀取固定字節數量的數據。
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
// 創建句柄
fi, err := os.Open("a.txt")
if err != nil {
panic(err)
}
// 創建 Reader
r := bufio.NewReader(fi)
// 每次讀取 1024 個字節
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 字節的數據,並發送到通道中,由另外一個協程進行讀取並打印出來。
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()
}