超全總結:Go 語言如何操作文件
前言
哈嘍,大家好,我是
asong
。我們都知道在
Unix
中萬物都被稱爲文件,文件處理是一個非常常見的問題,所以本文就總結了Go
語言操作文件的常見方式,整體思路如下:
Go 語言版本:1.18
本文所有代碼已經上傳github
:https://github.com/asong2020/Golang_Dream/tree/master/code_demo/file_operate_demo
操作文件包括哪些
操作一個文件離不開這幾個動作:
-
創建文件
-
打開文件
-
讀取文件
-
寫入文件
-
關閉文件
-
打包 / 解包
-
壓縮 / 解壓縮
-
改變文件權限
-
刪除文件
-
移動文件
-
重命名文件
-
清空文件
所以本文就針對這些操作總結了一些示例方法供大家參考;
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()
}
}
zip
打包示例:
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)
}
}
總結
本文歸根結底是介紹os
、io
、bufio
這些包如何操作文件,因爲Go
語言操作提供了太多了方法,藉着本文全都介紹出來,在使用的時候可以很方便的當作文檔查詢,如果你問用什麼方法操作文件是最優的方法,這個我也沒法回答你,需要根據具體場景分析的,如果這些方法你都知道了,在寫一個 benchmark 對比一下就可以了,實踐纔是檢驗真理的唯一標準。
本文所有代碼已經上傳github
:https://github.com/asong2020/Golang_Dream/tree/master/code_demo/file_operate_demo
好啦,本文到這裏就結束了,我是 asong,我們下期見。
Golang 夢工廠 asong 是一名後端程序員,目前就職於一家電商公司,專注於 Golang 技術,定期分享 Go 語言、MySQL、Redis、Elasticsearch、計算機基礎、微服務架構設計、面試等知識。這裏不僅有技術,還有故事!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/r30z2i_sSuRfEnM_23wFlg