重學Go語言之文件操作詳解
有很多場景都需要對文件進行讀取或者寫入,比如讀取配置文件或者寫入日志文件,除此之外,有時候我們也需要修改文件名稱,遍歷目錄內(nèi)的文件,刪除文件,在Go
語言中,操作文件應該算是一件比較簡單的事情,我們在這一篇文章中,一起來探究一下。
文件句柄:os.File
在Go
語言中,標準庫os
包下的File
結(jié)構(gòu)體表示一個文件句柄,獲得了os.File
,就可以對文件進行各種操作,要獲得一個os.File
文件句柄,有以下三種方式:
os.Create
通過os.Create()
函數(shù)傳入一個文件名稱可以創(chuàng)建并獲得一個表示該文件的os.File
結(jié)構(gòu)體:
file,err := os.Create("./my.dat")
如果指定的文件不存在,調(diào)用該函數(shù)后,會創(chuàng)建文件,如果文件已經(jīng)存在,則只會清空文件的內(nèi)容。
os.Open
對于已經(jīng)存在的文件,如果不想清空文件內(nèi)容,只想打開該文件的話,可以用os.Open()
函數(shù):
file, err := os.Open("./my.dat")
用該函數(shù)打開一個文件句柄時,如果文件不存在,返回值err
返回一個error
類型的錯誤。
os.OpenFile
其實,os.Create()
函數(shù)和os.Open()
函數(shù)的底層都是調(diào)用os.OpenFile()
函數(shù),這一點從os.Creat()
和os.Open()
函數(shù)的源碼可以得到證實:
//該源碼位于標準庫os包下的file.go文件中 func Open(name string) (*File, error) { return OpenFile(name, O_RDONLY, 0) } func Create(name string) (*File, error) { return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666) }
os.OpenFile()
函數(shù)簽名如下:
func OpenFile(name string, flag int, perm FileMode) (*File, error)
從函數(shù)簽名可以看到調(diào)用os.OpenFile
函數(shù)時要傳入三個參數(shù),其中name
是表示要打開的文件名。
而第二個參數(shù)flag
表示打開文件的標志,比較常用有以下幾種取值:
- O_RDONLY:只讀
- O_WRONLY:只寫
- O_RDWR:讀寫
- O_APPEND:以追加的方式寫入
- O_CREATE:文件不存在時創(chuàng)建
- O_TRUNC:當文件存在時,將文件內(nèi)容置空
可以同時指定多個標志,多個標志用邏輯運算符|
連接起來,比如os.Create()
函數(shù)在調(diào)用os.OpenFile
函數(shù)時就傳入多個標志:
OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
os.OpenFile()
函數(shù)的第三個參數(shù) FileMode
就是指類Unix
操作系統(tǒng)中的文件讀寫權(quán)限,即r
(讀)、w
(寫),x
(執(zhí)行),一個文件的rwx
有三組:
-rw-r--r-- 1 root staff 215 4 17 11:14 main.go
-rw-r--r--
分別表示文件擁有者、擁有者所在群組、其他人對該文件的權(quán)限,如果沒有該權(quán)限用-
表示。
rwx
用八進制表示時r=4,w=2,x=1
,所以上面main.go
文件權(quán)限用八進制表示為0644
。
無論是用哪種方式打開的文件句柄,最終都要記得關(guān)閉以釋放資源,比較標準的用法是用defer
語句:
name := "./my.dat" file,err := OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0755) if err != nil{ panic(err) } defer file.Close()
讀取文件
要讀取文件的內(nèi)容,在獲得文件句柄時,OpenFile
函數(shù)的參數(shù)flag
只需要傳O_RDONLY
就可以了,而參數(shù)FileMode
可以為0
:
file, err := os.OpenFile("./my.dat", os.O_RDONLY, 0)
os.File
有一個Read()
方法,也就是說os.File
實現(xiàn)io.Reader
接口,Go標準庫很多的包可以處理io.Reader
接口,比如ioutil
,bufio
,fmt
等,因此有很多種方式可以讀取文件的內(nèi)容。
直接讀取
os.File
的Read
方法就可以直接將文件內(nèi)容讀取一個字節(jié)數(shù)組中,并返回讀取的長度和一個用于判斷是否出錯的error
類型:
package main import ( "fmt" "io" "os" ) func main() { f, err := os.Open("./my.dat") if err != nil { panic(err) } defer f.Close() for { b := make([]byte, 10) n, err := f.Read(b) //將文件內(nèi)容讀取到字節(jié)數(shù)組中 if n == 0 || err == io.EOF { return } fmt.Println(n, string(b)) } }
在上面的例子中,我們循環(huán)讀取文件內(nèi)容,直到讀取到文件末尾時,返回的字節(jié)長度0
和一個io.EOF
的error類型,這時候表示文件已經(jīng)讀完,可以結(jié)束讀取了。
使用bufio包讀取文件
當要用bufio
包讀取文件時,將調(diào)用bufio.NewReader()
函數(shù)將io.Reader
包裝為一個bufio.Reader
結(jié)構(gòu)體,該結(jié)構(gòu)體封裝了很多更便捷讀取文件的方法:
func (b *Reader) Read(p []byte) (n int, err error) func (b *Reader) ReadByte() (byte, error) func (b *Reader) ReadBytes(delim byte) ([]byte, error) func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) func (b *Reader) ReadRune() (r rune, size int, err error) func (b *Reader) ReadSlice(delim byte) (line []byte, err error) func (b *Reader) ReadString(delim byte) (string, error)
下面是ReadLine
方法的使用示例:
package main import ( "bufio" "fmt" "io" "os" ) func main() { file, err := os.OpenFile("./my.dat", os.O_RDWR, 0666) if err != nil { panic(err) } defer file.Close() reader := bufio.NewReader(file) for { //按行讀取 b, _, err := reader.ReadLine() if err == io.EOF { break } fmt.Println(string(b)) } }
使用fmt包讀取文件
fmt
包以FScan...
開頭的函數(shù)可以按一定的格式掃描讀取文件里的內(nèi)容:
func Fscan(r io.Reader, a ...any) (n int, err error) func Fscanf(r io.Reader, format string, a ...any) (n int, err error) func Fscanln(r io.Reader, a ...any) (n int, err error)
下面是Fscanln
方法的使用示例:
package main import ( "fmt" "io" "os" ) func main() { file, err := os.OpenFile("./my.dat", os.O_RDWR, 0666) if err != nil { panic(err) } defer file.Close() for { var a1, a2 string _, err := fmt.Fscanln(file, &a1, &a2) fmt.Println(a2, a2) if err == io.EOF { break } } }
使用ioutil包讀取文件
標準庫的ioutil
包對讀取文件做好封裝,可以直接讀取整個文件的數(shù)據(jù):
f, err := os.Open("./my.dat") if err != nil { panic(err) } var b []byte b,err := ioutil.ReadAll(f)
ioutil
甚至封裝了直接讀取文件的函數(shù):
var b []byte b,err := ioutil.ReadFile("./my.dat")
寫入文件
要向文件寫入內(nèi)容,在調(diào)用OpenFile()
函數(shù)獲得句柄時flag參數(shù)要傳入O_WRONLY
或者O_RDWR
,如果是要往文件中以追加的形式在文件后面插入內(nèi)容,還是需要O_APPEND
:
OpenFile(name, O_RDWR|O_CREATE|O_APPEND, 0666)
os.File
有Write
方法,也就是說os.File
也實現(xiàn)了io.Writer
接口,所以同樣可以調(diào)用fmt
、bufio
、ioutil
包將數(shù)據(jù)寫入到文件中。
直接寫入
寫入文件最簡單的方式就是調(diào)用os.File
類型的Write
方法寫入一個字節(jié)數(shù)組:
package main import "os" func main() { file, err := os.OpenFile("./my.dat", os.O_RDWR, 0666) if err != nil { panic(file) } defer file.Close() file.Write([]byte("test222222")) }
也可以調(diào)用os.File
的WriteString
直接寫入一個字符串:
file.WriteString("test222222")
使用bufio包寫入文件
bufio
包的NewWriter
可以將一個io.Writer
包裝為bufio.Writer
結(jié)構(gòu)體,該結(jié)構(gòu)體主要有以下幾個方法可以將數(shù)據(jù)寫入文件:
func (b *Writer) Write(p []byte) (nn int, err error) func (b *Writer) WriteByte(c byte) error func (b *Writer) WriteRune(r rune) (size int, err error) func (b *Writer) WriteString(s string) (int, error)
上面幾個方法似乎與io.File
自身所擁有的方法差別不大,但bufio
包的寫入是帶有緩沖區(qū)的,也就是說當我們寫入數(shù)據(jù)時,不是立刻寫入到文件,而是寫到內(nèi)存緩沖區(qū),最后調(diào)用Flush
方法才將數(shù)據(jù)寫入文件。
package main import ( "bufio" "os" ) func main() { file, err := os.OpenFile("./my.dat", os.O_RDWR, 0666) if err != nil { panic(err) } defer file.Close() writer := bufio.NewWriter(file) writer.Write([]byte("111111111")) writer.Flush() }
使用fmt包寫入文件
fmt
包以下三個函數(shù)可以將格式化的數(shù)據(jù)寫入到一個io.Writer
:
func Fprint(w io.Writer, a ...any) (n int, err error) func Fprintf(w io.Writer, format string, a ...any) (n int, err error) func Fprintln(w io.Writer, a ...any) (n int, err error)
下面是使用fmt寫入文件的示例:
package main import ( "fmt" "os" ) func main() { file, err := os.OpenFile("./my.dat", os.O_RDWR, 0666) if err != nil { panic(err) } defer file.Close() fmt.Fprintf(file, "%s:%s", "username", "test") }
使用ioutil包寫入文件
同樣的,ioutil
包也對寫入文件做了封裝,使用一個函數(shù)便可以完成上碼代碼要完成的事情:
ioutil.WriteFile("./my.dat", []byte("22222"), 0666)
判斷是否為目錄
要判斷文件是否為目錄,在獲得os.File
對象,可以調(diào)用該對象的Stat
方法,該返回返回一個實現(xiàn)了os.FileInfo
接口的對象:
type FileInfo interface { Name() string // base name of the file Size() int64 // length in bytes for regular files; system-dependent for others Mode() FileMode // file mode bits ModTime() time.Time // modification time IsDir() bool // abbreviation for Mode().IsDir() Sys() any // underlying data source (can return nil) }
示例:
fileInfo, err := file.Stat() if fileInfo.IsDir() { fmt.Println(fileInfo.Name()) }
遍歷目錄
如果想遍歷目錄,可以調(diào)用os.ReadDir()
函數(shù),該函數(shù)返回一個元素類型為os.DirEntry
的切片:
func (f *File) ReadDir(n int) ([]DirEntry, error)
os.DirEntry
是一個接口,其定義如下:
type DirEntry interface { Name() string IsDir() bool Type() FileMode Info() (FileInfo, error) }
可以看到DirEntry
接口也有IsDir()
方法,因為可以再往下遍歷,下面是一個實現(xiàn)目錄遍歷的示例:
package main import ( "fmt" "log" "os" ) func main() { base := "./" IterateFolder(base) } func IterateFolder(base string) { dirEntry, err := os.ReadDir(base) if err != nil { log.Fatal(err) } for _, v := range dirEntry { if v.IsDir() { IterateFolder(base + "/" + v.Name()) } else { fmt.Println(v.Name()) } } }
修改文件名稱
修改文件名稱用os.Rename
函數(shù):
err := os.Rename("./1.txt", "./2.txt")
os.Rename
函數(shù)也可以用于移動文件:
err := os.Rename("./1.txt", "./m/2.txt")
刪除文件
刪除一個文件或者一個空目錄,直接調(diào)用os
包的Remove()
函數(shù)即可:
fileName := "./1.txt" os.Remove(fileName)
可以根據(jù)error
的返回值是否為nil判斷是否刪除成功,比如我們刪除一個不存在的文件或者刪除一個非空的目錄:
//m為當前目錄下一個非空目錄 err := os.Remove("./m") fmt.Println(err)
執(zhí)行結(jié)果:
remove ./m: directory not empty
對于非空目錄,如果要刪除,可以用os
包的RemoveAll
函數(shù):
err := os.RemoveAll("./m")
小結(jié)
好了,至此,相信你對于在Go
語言如何讀取和寫入文件應該掌握了吧,總結(jié)一下,在這篇文章中,主要講了以下幾點:
- 如何獲得一個文件句柄
os.File
。 - 如何以不同的方式讀取文件內(nèi)容。
- 如何以不同的方式向文件寫入內(nèi)容。
- 對文件的不同操作。
以上就是重學Go語言之文件操作詳解的詳細內(nèi)容,更多關(guān)于Go文件操作的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
一百行Golang代碼實現(xiàn)簡單并發(fā)聊天室
這篇文章主要為大家詳細介紹了一百行Golang代碼如何實現(xiàn)簡單并發(fā)聊天室,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-08-08Golang微服務框架Kratos實現(xiàn)分布式任務隊列Asynq的方法詳解
任務隊列(Task Queue) 一般用于跨線程或跨計算機分配工作的一種機制,在Golang語言里面,我們有像Asynq和Machinery這樣的類似于Celery的分布式任務隊列,本文就給大家詳細介紹一下Golang微服務框架Kratos實現(xiàn)分布式任務隊列Asynq的方法,需要的朋友可以參考下2023-09-09