最近在使用Golang進行文件讀寫的過程中,遇到幾個細節問題導致程序寫入數據時有一定臟數據的殘留,最后發現是使用os.OpenFile
在進行文件操作的時候沒有使用正確的flag
造成的。因此專門去學習了下Golang中讀寫文件的幾種方式方法。
讀文件
使用golang語言去讀取一個文件默認會有多種方式,這里主要介紹以下幾種。
使用ioutil
直接讀取
需要引入io/ioutil
包,該包默認擁有以下函數供用戶調用。
func NopCloser(r io.Reader) io.ReadCloser func ReadAll(r io.Reader) ([]byte, error) func ReadDir(dirname string) ([]os.FileInfo, error) func ReadFile(filename string) ([]byte, error) func TempDir(dir, prefix string) (name string, err error) func TempFile(dir, prefix string) (f *os.File, err error) func WriteFile(filename string, data []byte, perm os.FileMode) error
讀文件,我們可以看以下三個函數:
//從一個io.Reader類型中讀取內容直到返回錯誤或者EOF時返回讀取的數據,當err == nil時,數據成功讀取到[]byte中 //ReadAll函數被定義為從源中讀取數據直到EOF,它是不會去從返回數據中去判斷EOF來作為讀取成功的依據 func ReadAll(r io.Reader) ([]byte, error) //讀取一個目錄,並返回一個當前目錄下的文件對象列表和錯誤信息 func ReadDir(dirname string) ([]os.FileInfo, error) //讀取文件內容,並返回[]byte數據和錯誤信息。err == nil時,讀取成功 func ReadFile(filename string) ([]byte, error)
讀取文件示例:
$ cat readfile.go package main import ( "fmt" "io/ioutil" "strings" ) func main() { Ioutil("mytestfile.txt") } func Ioutil(name string) { if contents,err := ioutil.ReadFile(name);err == nil { //因為contents是[]byte類型,直接轉換成string類型后會多一行空格,需要使用strings.Replace替換換行符 result := strings.Replace(string(contents),"\n","",1) fmt.Println(result) } } $ go run readfile.go xxbandy.github.io @by Andy_xu
借助os.Open
進行讀取文件
由於os.Open
是打開一個文件並返回一個文件對象,因此其實可以結合ioutil.ReadAll(r io.Reader)
來進行讀取。io.Reader
其實是一個包含Read
方法的接口類型,而文件對象本身是實現了了Read
方法的。
我們先來看下os.Open
家族的相關函數
//打開一個需要被讀取的文件,如果成功讀取,返回的文件對象將可用被讀取,該函數默認的權限為O_RDONLY,也就是只對文件有只讀權限。如果有錯誤,將返回*PathError類型 func Open(name string) (*File, error) //大部分用戶會選擇該函數來代替Open or Create函數。該函數主要用來指定參數(os.O_APPEND|os.O_CREATE|os.O_WRONLY)以及文件權限(0666)來打開文件,如果打開成功返回的文件對象將被用作I/O操作 func OpenFile(name string, flag int, perm FileMode) (*File, error)
使用os.Open
家族函數和ioutil.ReadAll()
讀取文件示例:
func OsIoutil(name string) { if fileObj,err := os.Open(name);err == nil { //if fileObj,err := os.OpenFile(name,os.O_RDONLY,0644); err == nil { defer fileObj.Close() if contents,err := ioutil.ReadAll(fileObj); err == nil { result := strings.Replace(string(contents),"\n","",1) fmt.Println("Use os.Open family functions and ioutil.ReadAll to read a file contents:",result) } } } # 在main函數中調用OsIoutil(name)函數就可以讀取文件內容了 $ go run readfile.go Use os.Open family functions and ioutil.ReadAll to read a file contents: xxbandy.github.io @by Andy_xu
然而上述方式會比較繁瑣一些,因為使用了os
的同時借助了ioutil
,但是在讀取大文件的時候還是比較有優勢的。不過讀取小文件可以直接使用文件對象的一些方法。
不論是上邊說的os.Open
還是os.OpenFile
他們最終都返回了一個*File
文件對象,而該文件對象默認是有很多方法的,其中讀取文件的方法有如下幾種:
//從文件對象中讀取長度為b的字節,返回當前讀到的字節數以及錯誤信息。因此使用該方法需要先初始化一個符合內容大小的空的字節列表。讀取到文件的末尾時,該方法返回0,io.EOF func (f *File) Read(b []byte) (n int, err error) //從文件的off偏移量開始讀取長度為b的字節。返回讀取到字節數以及錯誤信息。當讀取到的字節數n小於想要讀取字節的長度len(b)的時候,該方法將返回非空的error。當讀到文件末尾時,err返回io.EOF func (f *File) ReadAt(b []byte, off int64) (n int, err error)
使用文件對象的Read
方法讀取:
func FileRead(name string) { if fileObj,err := os.Open(name);err == nil { defer fileObj.Close() //在定義空的byte列表時盡量大一些,否則這種方式讀取內容可能造成文件讀取不完整 buf := make([]byte, 1024) if n,err := fileObj.Read(buf);err == nil { fmt.Println("The number of bytes read:"+strconv.Itoa(n),"Buf length:"+strconv.Itoa(len(buf))) result := strings.Replace(string(buf),"\n","",1) fmt.Println("Use os.Open and File's Read method to read a file:",result) } } }
使用os.Open
和bufio.Reader
讀取文件內容
bufio
包實現了緩存IO,它本身包裝了io.Reader
和io.Writer
對象,創建了另外的Reader和Writer對象,不過該種方式是帶有緩存的,因此對於文本I/O來說,該包是提供了一些便利的。
先看下bufio
模塊下的相關的Reader
函數方法:
//首先定義了一個用來緩沖io.Reader對象的結構體,同時該結構體擁有以下相關的方法 type Reader struct { } //NewReader函數用來返回一個默認大小buffer的Reader對象(默認大小好像是4096) 等同於NewReaderSize(rd,4096) func NewReader(rd io.Reader) *Reader //該函數返回一個指定大小buffer(size最小為16)的Reader對象,如果 io.Reader參數已經是一個足夠大的Reader,它將返回該Reader func NewReaderSize(rd io.Reader, size int) *Reader //該方法返回從當前buffer中能被讀到的字節數 func (b *Reader) Buffered() int //Discard方法跳過后續的 n 個字節的數據,返回跳過的字節數。如果0 <= n <= b.Buffered(),該方法將不會從io.Reader中成功讀取數據。 func (b *Reader) Discard(n int) (discarded int, err error) //Peekf方法返回緩存的一個切片,該切片只包含緩存中的前n個字節的數據 func (b *Reader) Peek(n int) ([]byte, error) //把Reader緩存對象中的數據讀入到[]byte類型的p中,並返回讀取的字節數。讀取成功,err將返回空值 func (b *Reader) Read(p []byte) (n int, err error) //返回單個字節,如果沒有數據返回err func (b *Reader) ReadByte() (byte, error) //該方法在b中讀取delimz之前的所有數據,返回的切片是已讀出的數據的引用,切片中的數據在下一次的讀取操作之前是有效的。如果未找到delim,將返回查找結果並返回nil空值。因為緩存的數據可能被下一次的讀寫操作修改,因此一般使用ReadBytes或者ReadString,他們返回的都是數據拷貝 func (b *Reader) ReadSlice(delim byte) (line []byte, err error) //功能同ReadSlice,返回數據的拷貝 func (b *Reader) ReadBytes(delim byte) ([]byte, error) //功能同ReadBytes,返回字符串 func (b *Reader) ReadString(delim byte) (string, error) //該方法是一個低水平的讀取方式,一般建議使用ReadBytes('\n') 或 ReadString('\n'),或者使用一個 Scanner來代替。ReadLine 通過調用 ReadSlice 方法實現,返回的也是緩存的切片,用於讀取一行數據,不包括行尾標記(\n 或 \r\n) func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) //讀取單個UTF-8字符並返回一個rune和字節大小 func (b *Reader) ReadRune() (r rune, size int, err error)
示例:
func BufioRead(name string) { if fileObj,err := os.Open(name);err == nil { defer fileObj.Close() //一個文件對象本身是實現了io.Reader的 使用bufio.NewReader去初始化一個Reader對象,存在buffer中的,讀取一次就會被清空 reader := bufio.NewReader(fileObj) //使用ReadString(delim byte)來讀取delim以及之前的數據並返回相關的字符串. if result,err := reader.ReadString(byte('@'));err == nil { fmt.Println("使用ReadSlince相關方法讀取內容:",result) } //注意:上述ReadString已經將buffer中的數據讀取出來了,下面將不會輸出內容 //需要注意的是,因為是將文件內容讀取到[]byte中,因此需要對大小進行一定的把控 buf := make([]byte,1024) //讀取Reader對象中的內容到[]byte類型的buf中 if n,err := reader.Read(buf); err == nil { fmt.Println("The number of bytes read:"+strconv.Itoa(n)) //這里的buf是一個[]byte,因此如果需要只輸出內容,仍然需要將文件內容的換行符替換掉 fmt.Println("Use bufio.NewReader and os.Open read file contents to a []byte:",string(buf)) } } }
讀取文件全部示例
/** * @File Name: readfile.go * @Author:Andy_xu @xxbandy.github.io * @Email:371990778@qq.com * @Create Date: 2017-12-16 16:12:01 * @Last Modified: 2017-12-17 12:12:02 * @Description:讀取指定文件的幾種方法,需要注意的是[]byte類型在轉換成string類型的時候,都會在最后多一行空格,需要使用result := strings.Replace(string(contents),"\n","",1) 方式替換換行符 */ package main import ( "fmt" "io/ioutil" "strings" "os" "strconv" "bufio" ) func main() { Ioutil("mytestfile.txt") OsIoutil("mytestfile.txt") FileRead("mytestfile.txt") BufioRead("mytestfile.txt") } func Ioutil(name string) { if contents,err := ioutil.ReadFile(name);err == nil { //因為contents是[]byte類型,直接轉換成string類型后會多一行空格,需要使用strings.Replace替換換行符 result := strings.Replace(string(contents),"\n","",1) fmt.Println("Use ioutil.ReadFile to read a file:",result) } } func OsIoutil(name string) { if fileObj,err := os.Open(name);err == nil { //if fileObj,err := os.OpenFile(name,os.O_RDONLY,0644); err == nil { defer fileObj.Close() if contents,err := ioutil.ReadAll(fileObj); err == nil { result := strings.Replace(string(contents),"\n","",1) fmt.Println("Use os.Open family functions and ioutil.ReadAll to read a file :",result) } } } func FileRead(name string) { if fileObj,err := os.Open(name);err == nil { defer fileObj.Close() //在定義空的byte列表時盡量大一些,否則這種方式讀取內容可能造成文件讀取不完整 buf := make([]byte, 1024) if n,err := fileObj.Read(buf);err == nil { fmt.Println("The number of bytes read:"+strconv.Itoa(n),"Buf length:"+strconv.Itoa(len(buf))) result := strings.Replace(string(buf),"\n","",1) fmt.Println("Use os.Open and File's Read method to read a file:",result) } } } func BufioRead(name string) { if fileObj,err := os.Open(name);err == nil { defer fileObj.Close() //一個文件對象本身是實現了io.Reader的 使用bufio.NewReader去初始化一個Reader對象,存在buffer中的,讀取一次就會被清空 reader := bufio.NewReader(fileObj) //使用ReadString(delim byte)來讀取delim以及之前的數據並返回相關的字符串. if result