Go 語言讀文件的九種方法


查看本系列教程目錄,請點擊 零基礎小白入門 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. 每次只讀取一行

一次性讀取所有的數據,太耗費內存,因此可以指定每次只讀取一行數據。方法有三種:

  1. bufio.ReadLine()
  2. bufio.ReadBytes('\n')
  3. 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()
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM