Go語言操作檔案可使用的庫
Go語言官方庫:os、io/ioutil、bufio涵蓋了檔案操作的所有場景,os提供了對檔案IO直接調用的方法,bufio提供緩沖區操作檔案的方法,io/ioutil也提供對檔案IO直接調用的方法,不過Go語言在Go1.16版本已經棄用了io/ioutil庫,這個io/ioutil包是一個定義不明确且難以了解的東西集合。該軟體包提供的所有功能都已移至其他軟體包,是以io/ioutil中操作檔案的方法都在io庫有相同含義的方法,大家以後在使用到ioutil中的方法是可以通過注釋在其他包找到對應的方法。
檔案的基礎操作
這裡我把 建立檔案、打開檔案、關閉檔案、改變檔案權限這些歸為對檔案的基本操作,對檔案的基本操作直接使用os庫中的方法即可,因為我們需要進行IO操作,來看下面的例子:
import (
"log"
"os"
)
func main() {
// 建立檔案
f, err := os.Create("asong.txt")
if err != nil{
log.Fatalf("create file failed err=%s\n", err)
}
// 擷取檔案資訊
fileInfo, err := f.Stat()
if err != nil{
log.Fatalf("get file info failed err=%s\n", err)
}
log.Printf("File Name is %s\n", fileInfo.Name())
log.Printf("File Permissions is %s\n", fileInfo.Mode())
log.Printf("File ModTime is %s\n", fileInfo.ModTime())
// 改變檔案權限
err = f.Chmod(0777)
if err != nil{
log.Fatalf("chmod file failed err=%s\n", err)
}
// 改變擁有者
err = f.Chown(os.Getuid(), os.Getgid())
if err != nil{
log.Fatalf("chown file failed err=%s\n", err)
}
// 再次擷取檔案資訊 驗證改變是否正确
fileInfo, err = f.Stat()
if err != nil{
log.Fatalf("get file info second failed err=%s\n", err)
}
log.Printf("File change Permissions is %s\n", fileInfo.Mode())
// 關閉檔案
err = f.Close()
if err != nil{
log.Fatalf("close file failed err=%s\n", err)
}
// 删除檔案
err = os.Remove("asong.txt")
if err != nil{
log.Fatalf("remove file failed err=%s\n", err)
}
}
寫檔案
快寫檔案
os/ioutil包都提供了WriteFile方法可以快速處理建立/打開檔案/寫資料/關閉檔案,使用示例如下:
func writeAll(filename string) error {
err := os.WriteFile("asong.txt", []byte("Hi asong\n"), 0666)
if err != nil {
return err
}
return nil
}
按行寫檔案
os、buffo寫資料都沒有提供按行寫入的方法,是以我們可以在調用os.WriteString、bufio.WriteString方法是在資料中加入換行符即可,來看示例:
import (
"bufio"
"log"
"os"
)
// 直接操作IO
func writeLine(filename string) error {
data := []string{
"asong",
"test",
"123",
}
f, err := os.OpenFile(filename, os.O_WRONLY, 0666)
if err != nil{
return err
}
for _, line := range data{
_,err := f.WriteString(line + "\n")
if err != nil{
return err
}
}
f.Close()
return nil
}
// 使用緩存區寫入
func writeLine2(filename string) error {
file, err := os.OpenFile(filename, os.O_WRONLY, 0666)
if err != nil {
return err
}
// 為這個檔案建立buffered writer
bufferedWriter := bufio.NewWriter(file)
for i:=0; i < 2; i++{
// 寫字元串到buffer
bytesWritten, err := bufferedWriter.WriteString(
"asong真帥\n",
)
if err != nil {
return err
}
log.Printf("Bytes written: %d\n", bytesWritten)
}
// 寫記憶體buffer到硬碟
err = bufferedWriter.Flush()
if err != nil{
return err
}
file.Close()
return nil
}
偏移量寫入
某些場景我們想根據給定的偏移量寫入資料,可以使用os中的writeAt方法,例子如下:
import "os"
func writeAt(filename string) error {
data := []byte{
0x41, // A
0x73, // s
0x20, // space
0x20, // space
0x67, // g
}
f, err := os.OpenFile(filename, os.O_WRONLY, 0666)
if err != nil{
return err
}
_, err = f.Write(data)
if err != nil{
return err
}
replaceSplace := []byte{
0x6F, // o
0x6E, // n
}
_, err = f.WriteAt(replaceSplace, 2)
if err != nil{
return err
}
f.Close()
return nil
}
緩存區寫入
os庫中的方法對檔案都是直接的IO操作,頻繁的IO操作會增加CPU的中斷頻率,是以我們可以使用記憶體緩存區來減少IO操作,在寫位元組到硬碟前使用記憶體緩存,當記憶體緩存區的容量到達一定數值時在寫記憶體資料buffer到硬碟,bufio就是這樣示一個庫,來個例子我們看一下怎麼使用:
import (
"bufio"
"log"
"os"
)
func writeBuffer(filename string) error {
file, err := os.OpenFile(filename, os.O_WRONLY, 0666)
if err != nil {
return err
}
// 為這個檔案建立buffered writer
bufferedWriter := bufio.NewWriter(file)
// 寫字元串到buffer
bytesWritten, err := bufferedWriter.WriteString(
"asong真帥\n",
)
if err != nil {
return err
}
log.Printf("Bytes written: %d\n", bytesWritten)
// 檢查緩存中的位元組數
unflushedBufferSize := bufferedWriter.Buffered()
log.Printf("Bytes buffered: %d\n", unflushedBufferSize)
// 還有多少位元組可用(未使用的緩存大小)
bytesAvailable := bufferedWriter.Available()
if err != nil {
return err
}
log.Printf("Available buffer: %d\n", bytesAvailable)
// 寫記憶體buffer到硬碟
err = bufferedWriter.Flush()
if err != nil{
return err
}
file.Close()
return nil
}
讀檔案
讀取全檔案
有兩種方式我們可以讀取全檔案:
os、io/ioutil中提供了readFile方法可以快速讀取全文
io/ioutil中提供了ReadAll方法在打開檔案句柄後可以讀取全文;
import (
"io/ioutil"
"log"
"os"
)
func readAll(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return err
}
log.Printf("read %s content is %s", filename, data)
return nil
}
func ReadAll2(filename string) error {
file, err := os.Open("asong.txt")
if err != nil {
return err
}
content, err := ioutil.ReadAll(file)
log.Printf("read %s content is %s\n", filename, content)
file.Close()
return nil
}
逐行讀取
os庫中提供了Read方法是按照位元組長度讀取,如果我們想要按行讀取檔案需要配合bufio一起使用,bufio中提供了三種方法ReadLine、ReadBytes("\n")、ReadString("\n")可以按行讀取資料,下面我使用ReadBytes("\n")來寫個例子:
func readLine(filename string) error {
file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
if err != nil {
return err
}
bufferedReader := bufio.NewReader(file)
for {
// ReadLine is a low-level line-reading primitive. Most callers should use
// ReadBytes('\n') or ReadString('\n') instead or use a Scanner.
lineBytes, err := bufferedReader.ReadBytes('\n')
bufferedReader.ReadLine()
line := strings.TrimSpace(string(lineBytes))
if err != nil && err != io.EOF {
return err
}
if err == io.EOF {
break
}
log.Printf("readline %s every line data is %s\n", filename, line)
}
file.Close()
return nil
}
按塊讀取檔案
有些場景我們想按照位元組長度讀取檔案,這時我們可以如下方法:
os庫的Read方法
os庫配合bufio.NewReader調用Read方法
os庫配合io庫的ReadFull、ReadAtLeast方法
// use bufio.NewReader
func readByte(filename string) error {
file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
if err != nil {
return err
}
// 建立 Reader
r := bufio.NewReader(file)
// 每次讀取 2 個位元組
buf := make([]byte, 2)
for {
n, err := r.Read(buf)
if err != nil && err != io.EOF {
return err
}
if n == 0 {
break
}
log.Printf("writeByte %s every read 2 byte is %s\n", filename, string(buf[:n]))
}
file.Close()
return nil
}
// use os
func readByte2(filename string) error{
file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
if err != nil {
return err
}
// 每次讀取 2 個位元組
buf := make([]byte, 2)
for {
n, err := file.Read(buf)
if err != nil && err != io.EOF {
return err
}
if n == 0 {
break
}
log.Printf("writeByte %s every read 2 byte is %s\n", filename, string(buf[:n]))
}
file.Close()
return nil
}
// use os and io.ReadAtLeast
func readByte3(filename string) error{
file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
if err != nil {
return err
}
// 每次讀取 2 個位元組
buf := make([]byte, 2)
for {
n, err := io.ReadAtLeast(file, buf, 0)
if err != nil && err != io.EOF {
return err
}
if n == 0 {
break
}
log.Printf("writeByte %s every read 2 byte is %s\n", filename, string(buf[:n]))
}
file.Close()
return nil
}
分隔符讀取
bufio包中提供了Scanner掃描器子產品,它的主要作用是把資料流分割成一個個标記并除去它們之間的空格,他支援我們定制Split函數做為分隔函數,分隔符可以不是一個簡單的位元組或者字元,我們可以自定義分隔函數,在分隔函數實作分隔規則以及指針移動多少,傳回什麼資料,如果沒有定制Split函數,那麼就會使用預設ScanLines作為分隔函數,也就是使用換行作為分隔符,bufio中還提供了預設方法ScanRunes、ScanWrods,下面我們用SacnWrods方法寫個例子,擷取用空格分隔的文本:
func readScanner(filename string) error {
file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
if err != nil {
return err
}
scanner := bufio.NewScanner(file)
// 可以定制Split函數做分隔函數
// ScanWords 是scanner自帶的分隔函數用來找空格分隔的文本字
scanner.Split(bufio.ScanWords)
for {
success := scanner.Scan()
if success == false {
// 出現錯誤或者EOF是傳回Error
err = scanner.Err()
if err == nil {
log.Println("Scan completed and reached EOF")
break
} else {
return err
}
}
// 得到資料,Bytes() 或者 Text()
log.Printf("readScanner get data is %s", scanner.Text())
}
file.Close()
return nil
}
打包/解包
Go語言的archive包中提供了tar、zip兩種打包/解包方法,這裡以zip的打包/解包為例子:
zip解包示例:
import (
"archive/zip"
"fmt"
"io"
"log"
"os"
)
func main() {
// Open a zip archive for reading.
r, err := zip.OpenReader("asong.zip")
if err != nil {
log.Fatal(err)
}
defer r.Close()
// Iterate through the files in the archive,
// printing some of their contents.
for _, f := range r.File {
fmt.Printf("Contents of %s:\n", f.Name)
rc, err := f.Open()
if err != nil {
log.Fatal(err)
}
_, err = io.CopyN(os.Stdout, rc, 68)
if err != nil {
log.Fatal(err)
}
rc.Close()
}
}
func writerZip() {
// Create archive
zipPath := "out.zip"
zipFile, err := os.Create(zipPath)
if err != nil {
log.Fatal(err)
}
// Create a new zip archive.
w := zip.NewWriter(zipFile)
// Add some files to the archive.
var files = []struct {
Name, Body string
}{
{"asong.txt", "This archive contains some text files."},
{"todo.txt", "Get animal handling licence.\nWrite more examples."},
}
for _, file := range files {
f, err := w.Create(file.Name)
if err != nil {
log.Fatal(err)
}
_, err = f.Write([]byte(file.Body))
if err != nil {
log.Fatal(err)
}
}
// Make sure to check the error on Close.
err = w.Close()
if err != nil {
log.Fatal(err)
}
}