一文詳解golang通過io包進行文件讀寫
在 golang 語言中,實現(xiàn)io的讀與寫,只要實現(xiàn)了如下的讀寫接口即可:
// go 1.19/src/io/io.go
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}golang 語言自身提供了很多實現(xiàn)這兩個接口的結(jié)構(gòu)體,比較典型的有:io、os、bytes.buffer、strings、bufio等,這些解決了文件的讀寫,字節(jié)、字符串的讀寫,或者是帶緩沖的讀寫等。
今天我們先來看看 io包 中提供的這些接口,簡單了解下相關(guān)原理。
1.io包的讀寫函數(shù)
典型的相關(guān)函數(shù)聲明如下:
// 讀操作
func ReadAll(r Reader) ([]byte, error) {...}
// 寫操作
func Copy(dst Writer, src Reader) (written int64, err error) {...}
func WriteString(w Writer, s string) (n int, err error) {...}在上面的 ReadAll() 函數(shù)中,接收一個 Reader 類型的參數(shù),比如 os.File 類型,又或者是其他的實現(xiàn)了 io.Reader 接口的結(jié)構(gòu)體類型,輸出讀取到的內(nèi)容,以字節(jié)數(shù)組形式輸出,外附一個 error 錯誤,我們進一步看看其內(nèi)部實現(xiàn):
func ReadAll(r Reader) ([]byte, error) {
// 新建字節(jié)數(shù)組,cap=512
b := make([]byte, 0, 512)
// 開啟循環(huán)讀取內(nèi)容
for {
//
if len(b) == cap(b) {
// Add more capacity (let append pick how much).
b = append(b, 0)[:len(b)]
}
// 讀取 reader中的內(nèi)容,填充到對應(yīng)部分
n, err := r.Read(b[len(b):cap(b)])
b = b[:len(b)+n]
// 如果讀到了結(jié)尾,可以返回數(shù)據(jù)
if err != nil {
if err == EOF {
err = nil
}
return b, err
}
}
}從上面源碼可以知道,io.ReadAll() 函數(shù),通過一次讀取 Reader 中的所有內(nèi)容,如果是小文件無所謂,占用內(nèi)存有限,但如果是好幾個G的文件呢,是不是,本來服務(wù)器內(nèi)存有限,以下占用幾G內(nèi)存,這樣讀取不太合理,當然,golang 也為我們提供了帶緩沖的讀取,這是后話,后面再討論。
接下來看看寫入操作的源碼實現(xiàn):
// io.Copy()
func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil) // 實際通過調(diào)用此函數(shù)實現(xiàn)
}
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
if buf != nil && len(buf) == 0 { // 當buf非空或者長度0,panic
panic("empty buffer in CopyBuffer")
}
return copyBuffer(dst, src, buf) // 繼續(xù)往下調(diào)用
}
func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
// If the reader has a WriteTo method, use it to do the copy.
// Avoids an allocation and a copy.
if wt, ok := src.(WriterTo); ok { // 源實現(xiàn)了writeTo就可以直接調(diào)用結(jié)束
return wt.WriteTo(dst)
}
// Similarly, if the writer has a ReadFrom method, use it to do the copy.
if rt, ok := dst.(ReaderFrom); ok { // 目標實現(xiàn)了readFrom,也可以直接調(diào)用結(jié)束
return rt.ReadFrom(src)
}
if buf == nil {
size := 32 * 1024
if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
if l.N < 1 {
size = 1
} else {
size = int(l.N)
}
}
buf = make([]byte, size) // buf空時,創(chuàng)建buf
}
// 開啟循環(huán)內(nèi)容讀取
for {
nr, er := src.Read(buf) // 從源中讀取內(nèi)容到buf中
if nr > 0 {
nw, ew := dst.Write(buf[0:nr]) // 目標w從buf中寫入內(nèi)容
if nw < 0 || nr < nw {
nw = 0
if ew == nil {
ew = errInvalidWrite
}
}
written += int64(nw) // 更新寫入長度
if ew != nil { // 寫入返回err,退出循環(huán)
err = ew
break
}
if nr != nw { // 讀取與寫入長度不等,退出
err = ErrShortWrite
break
}
}
if er != nil { // 讀err非空,退出
if er != EOF {
err = er
}
break
}
}
return written, err // 返回寫入長度及err
}通過源碼走讀,我們可以看到,在函數(shù)中傳入 Writer 作為目標寫入對象,傳入 Reader 作為源讀取對象,如果 Reader 對象實現(xiàn) WriteTo 方法,我們可以直接調(diào)用此完成 copy, 或者是 Writer 對象實現(xiàn)了 ReadFrom 方法,我們可以直接調(diào)用此完成 copy,都沒有實現(xiàn),就只有通過 buf 作為中轉(zhuǎn),通過循環(huán),先從源讀取內(nèi)容,再寫入目標對象中,最后返回整個內(nèi)容的長度,當然其中也有相關(guān)錯誤處理,這里不做討論。
接下來 io包 中提供的另外的寫入操作實現(xiàn):
// io.WriteString()
func WriteString(w Writer, s string) (n int, err error) {
if sw, ok := w.(StringWriter); ok { // 如w實現(xiàn)了 StringWriter,則調(diào)用相關(guān)方法實現(xiàn)寫入
return sw.WriteString(s)
}
return w.Write([]byte(s)) // 作為兜底,w對象是肯定實現(xiàn)了 Write() 方法,所以調(diào)用此方法實現(xiàn)寫入
}從上面代碼可以看到,該實現(xiàn)邏輯簡單,具體見注釋。
io包實現(xiàn)讀寫操作 準備:
- file.txt,作為讀取源,然后再寫入其他文件中 寫操作,我們實現(xiàn)了2種,一種
io.Copy(),一種io.WriteString(),具體使用見下面代碼:
func ioRW() {
// read
f, err := os.Open("D:\\demo1\\src\\demo23\\go-io\\file\\file.txt")
defer f.Close()
if err != nil {
fmt.Printf("err: %s\n", err)
return
}
b, err := io.ReadAll(f)
if err != nil {
fmt.Printf("err: %s\n", err)
return
}
fmt.Printf("ioRW read content: \n%s\n", b)
// write
// 1.io.Copy()
fw, err := os.Create("io_w.txt")
defer fw.Close()
n, err := io.Copy(fw, strings.NewReader(string(b)))
if err != nil {
fmt.Printf("err: %s\n", err)
return
}
// 2.io.WriteString()
fw_1, _ := os.Create("io_w_1.txt")
defer fw_1.Close()
n, err := io.WriteString(fw_1, string(b))
if err != nil {
fmt.Printf("err: %s\n", err)
return
}
fmt.Printf("ioRW write size: %d\n", n)
}2.io.ioutil包實現(xiàn)讀寫操作
所謂 util,肯定是作為工具使用,怎么方便怎么來,我們甚至通過文件名就可以完成操作,具體實現(xiàn)細節(jié)我們不關(guān)心,總之它實現(xiàn)了這些功能。
注意 由于我使用的 go 1.19,在源碼中,明確聲明在 go 1.16 后,逐漸棄用了,所以使用高版本的 go 時要注意這個問題,這里為了增加源碼的了解熟悉,也拿出來作為分享。
讀操作
// Deprecated: As of Go 1.16, this function simply calls io.ReadAll.
func ReadAll(r io.Reader) ([]byte, error) {
return io.ReadAll(r)
}
// Deprecated: As of Go 1.16, this function simply calls os.ReadFile.
func ReadFile(filename string) ([]byte, error) {
return os.ReadFile(filename)
}在 ReadAll 中,實際也是調(diào)用上面的 io.ReadAll(),這里不贅述,在 ReadFile 中,我們傳入個文件名,就可以讀取到整個字節(jié)內(nèi)容,實際它也是調(diào)用 os.ReadFile() 實現(xiàn),后面再討論 os包。
寫操作
// Deprecated: As of Go 1.16, this function simply calls os.WriteFile.
func WriteFile(filename string, data []byte, perm fs.FileMode) error {
return os.WriteFile(filename, data, perm)
}可以看到,這里也是調(diào)用了 os.WriteFile() 實現(xiàn)文件的寫入,只需要我們傳入待寫入的文件名,寫入的字節(jié)數(shù)組,以及寫入文件的權(quán)限,是不是很簡單。
代碼實用
func ioutilRW() {
// read
b, err := ioutil.ReadFile("D:\\demo1\\src\\demo23\\go-io\\file\\file.txt")
if err != nil {
fmt.Printf("err: %s\n", err)
return
}
fmt.Printf("ioutilRW read content: \n%s\n", b)
// write
err = ioutil.WriteFile("ioutilRW_w.txt", b, 664)
if err != nil {
fmt.Printf("err: %s\n", err)
return
}
fmt.Println("ioutilRW write err: ", err)
}上面的讀取與寫入操作實際很簡單,代碼操作沒難度,相信你一看就懂。
今天我們通過 io.Reader/io.Writer 接口了解了實現(xiàn)了該接口的都可以作為相關(guān)對象,通過 io包也實現(xiàn)了文件的讀寫,后面我們會陸續(xù)分享基于 os/bufio/bytes/strings 的讀寫操作,畢竟 IO 范圍還是很廣,文件IO只是一部分。
到此這篇關(guān)于一文詳解golang通過io包進行文件讀寫的文章就介紹到這了,更多相關(guān)golang通過io包文件讀寫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go實現(xiàn)文件的創(chuàng)建、刪除與讀取示例代碼
這篇文章主要給大家介紹了關(guān)于go如何實現(xiàn)文件的創(chuàng)建、刪除與讀取的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧2019-02-02
SingleFlight模式的Go并發(fā)編程學(xué)習(xí)
這篇文章主要為大家介紹了SingleFlight模式的Go并發(fā)編程學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-04-04
GoFrame框架數(shù)據(jù)校驗之校驗結(jié)果Error接口對象
這篇文章主要為大家介紹了GoFrame框架數(shù)據(jù)校驗之校驗結(jié)果Error接口對象示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06
golang實現(xiàn)http服務(wù)器處理靜態(tài)文件示例
這篇文章主要介紹了golang實現(xiàn)http服務(wù)器處理靜態(tài)文件的方法,涉及Go語言基于http協(xié)議處理文件的相關(guān)技巧,需要的朋友可以參考下2016-07-07

