溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Go語(yǔ)言中Writer 和 Reader如何使用

發(fā)布時(shí)間:2021-07-06 16:29:02 來(lái)源:億速云 閱讀:219 作者:Leah 欄目:編程語(yǔ)言

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)Go語(yǔ)言中Writer 和 Reader如何使用,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

輸入和輸出

Go Writer 和 Reader接口的設(shè)計(jì)遵循了Unix的輸入和輸出,一個(gè)程序的輸出可以是另外一個(gè)程序的輸入。他們的功能單一并且純粹,這樣就可以非常容易的編寫(xiě)程序代碼,又可以通過(guò)組合的概念,讓我們的程序做更多的事情。

比如我們?cè)谏弦黄腉o log中,就介紹了Unix的三種輸入輸出輸入模式,他們對(duì)應(yīng)的Go語(yǔ)言里有專門(mén)的實(shí)現(xiàn)。

var (
    Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
    Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
    Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

這三種標(biāo)準(zhǔn)的輸入和輸出都是一個(gè)*File,而*File恰恰就是同時(shí)實(shí)現(xiàn)了io.Writer和io.Reader這兩個(gè)接口的類型,所以它們同時(shí)具備輸入和輸出的功能,既可以從里面讀取數(shù)據(jù),又可以往里面寫(xiě)入數(shù)據(jù)。

Go標(biāo)準(zhǔn)庫(kù)的io包也是基于Unix這種輸入和輸出的理念,大部分的接口都是擴(kuò)展了io.Writer和io.Reader,大部分的類型也都選擇地實(shí)現(xiàn)了io.Writer和io.Reader這兩個(gè)接口,然后把數(shù)據(jù)的輸入和輸出,抽象為流的讀寫(xiě)。所以只要實(shí)現(xiàn)了這兩個(gè)接口,都可以使用流的讀寫(xiě)功能。

io.Writer和io.Reader兩個(gè)接口的高度抽象,讓我們不用再面向具體的業(yè)務(wù),我們只關(guān)注,是讀還是寫(xiě)。只要我們定義的方法函數(shù)可以接收這兩個(gè)接口作為參數(shù),那么我們就可以進(jìn)行流的讀寫(xiě),而不用關(guān)心如何讀、寫(xiě)到哪里去,這也是面向接口編程的好處。

Reader和Writer接口

這兩個(gè)高度抽象的接口,只有一個(gè)方法,也體現(xiàn)了Go接口設(shè)計(jì)的簡(jiǎn)潔性,只做一件事。

// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {    Write(p []byte) (n int, err error)
}

這是Wirter接口的定義,它只有一個(gè)Write方法。它接受一個(gè)byte的切片,返回兩個(gè)值,n表示寫(xiě)入的字節(jié)數(shù)、err表示寫(xiě)入時(shí)發(fā)生的錯(cuò)誤。

從其文檔注釋來(lái)看,這個(gè)方法是有規(guī)范要求的,我們要想實(shí)現(xiàn)一個(gè)io.Writer接口,就要遵循這些規(guī)則。

  • write方法向底層數(shù)據(jù)流寫(xiě)入len(p)字節(jié)的數(shù)據(jù),這些數(shù)據(jù)來(lái)自于切片p;

  • 返回被寫(xiě)入的字節(jié)數(shù)n,0 <= n <= len(p);

  • 如果n<len(p), 則必須返回一些非nil的err;

  • 如果中途出現(xiàn)問(wèn)題,也要返回非nil的err;

  • Write方法絕對(duì)不能修改切片p以及里面的數(shù)據(jù)。

這些實(shí)現(xiàn)io.Writer接口的規(guī)則,所有實(shí)現(xiàn)了該接口的類型都要遵守,不然可能會(huì)導(dǎo)致莫名其妙的問(wèn)題。

// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p. It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered. Even if Read
// returns n < len(p), it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, Read conventionally
// returns what is available instead of waiting for more.
//
// When Read encounters an error or end-of-file condition after
// successfully reading n > 0 bytes, it returns the number of
// bytes read. It may return the (non-nil) error from the same call
// or return the error (and n == 0) from a subsequent call.
// An instance of this general case is that a Reader returning
// a non-zero number of bytes at the end of the input stream may
// return either err == EOF or err == nil. The next Read should
// return 0, EOF.
//
// Callers should always process the n > 0 bytes returned before
// considering the error err. Doing so correctly handles I/O errors
// that happen after reading some bytes and also both of the
// allowed EOF behaviors.
//
// Implementations of Read are discouraged from returning a
// zero byte count with a nil error, except when len(p) == 0.
// Callers should treat a return of 0 and nil as indicating that
// nothing happened; in particular it does not indicate EOF.
//
// Implementations must not retain p.
type Reader interface {    Read(p []byte) (n int, err error)
}

這是io.Reader接口定義,也只有一個(gè)Read方法。這個(gè)方法接受一個(gè)byte的切片,并返回兩個(gè)值,一個(gè)是讀入的字節(jié)數(shù),一個(gè)是err錯(cuò)誤。

從其注釋文檔看,io.Reader接口的規(guī)則更多。

  • Read最多讀取len(p)字節(jié)的數(shù)據(jù),并保存到p;

  • 返回讀取的字節(jié)數(shù)以及任何發(fā)生的錯(cuò)誤信息;

  • n要滿足0 <= n <= len(p);

  • n<len(p)時(shí),表示讀取的數(shù)據(jù)不足以填滿p,這時(shí)方法會(huì)立即返回,而不是等待更多的數(shù)據(jù);

  • 讀取過(guò)程中遇到錯(cuò)誤,會(huì)返回讀取的字節(jié)數(shù)n以及相應(yīng)的錯(cuò)誤err;

  • 在底層輸入流結(jié)束時(shí),方法會(huì)返回n>0的字節(jié),但是err可能時(shí)EOF,也可以是nil;

  • 在第6種(上面)情況下,再次調(diào)用read方法的時(shí)候,肯定會(huì)返回0,EOF;

  • 調(diào)用Read方法時(shí),如果n>0時(shí),優(yōu)先處理處理讀入的數(shù)據(jù),然后再處理錯(cuò)誤err,EOF也要這樣處理;

  • Read方法不鼓勵(lì)返回n=0并且err=nil的情況。

規(guī)則稍微比Write接口有點(diǎn)多,不過(guò)也都比較好理解。注意第 8 條,即使我們?cè)谧x取的時(shí)候遇到錯(cuò)誤,但是也應(yīng)該處理已經(jīng)讀到的數(shù)據(jù)。因?yàn)檫@些已經(jīng)讀到的數(shù)據(jù)是正確的,如果不進(jìn)行處理丟失的話,讀到的數(shù)據(jù)就不完整了。

示例

對(duì)這兩個(gè)接口了解后,我們就可以嘗試使用他們了,現(xiàn)在來(lái)看個(gè)例子。

func main() {
    //定義零值Buffer類型變量b
    var b bytes.Buffer
    //使用Write方法為寫(xiě)入字符串
    b.Write([]byte("你好"))    
    //這個(gè)是把一個(gè)字符串拼接到Buffer里
    fmt.Fprint(&b,",","http://www.flysnow.org")    
    //把Buffer里的內(nèi)容打印到終端控制臺(tái)
    b.WriteTo(os.Stdout)
}

這個(gè)例子是拼接字符串到Buffer里,然后再輸出到控制臺(tái)。它非常簡(jiǎn)單,但是利用了流的讀寫(xiě),bytes.Buffer是一個(gè)可變字節(jié)的類型,可以讓我們很容易的對(duì)字節(jié)進(jìn)行操作,比如讀寫(xiě)、追加等。bytes.Buffer實(shí)現(xiàn)了io.Writer和io.Reader接口,所以我們可以很容易地進(jìn)行讀寫(xiě)操作,而不用關(guān)注具體實(shí)現(xiàn)。

b.Write([]byte("你好"))實(shí)現(xiàn)了寫(xiě)入一個(gè)字符串。我們把這個(gè)字符串轉(zhuǎn)為一個(gè)字節(jié)切片,然后調(diào)用Write方法寫(xiě)入,這個(gè)就是bytes.Buffer為了實(shí)現(xiàn)io.Writer接口而實(shí)現(xiàn)的一個(gè)方法,可以幫我們寫(xiě)入數(shù)據(jù)流。

func (b *Buffer) Write(p []byte) (n int, err error) {
    b.lastRead = opInvalid
    m := b.grow(len(p))
    return copy(b.buf[m:], p), nil
}

以上就是bytes.Buffer實(shí)現(xiàn)io.Writer接口的方法。最終我們看到,寫(xiě)入的切片會(huì)被拷貝到b.buf里,這里b.buf[m:]拷貝其實(shí)就是追加的意思,不會(huì)覆蓋已經(jīng)存在的數(shù)據(jù)。

從實(shí)現(xiàn)看,我們發(fā)現(xiàn)其實(shí)只有b *Buffer指針實(shí)現(xiàn)了io.Writer接口,所以我們示例代碼中調(diào)用fmt.Fprint函數(shù)的時(shí)候,傳遞的是一個(gè)地址&b。

func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
    p := newPrinter()
    p.doPrint(a)
    n, err = w.Write(p.buf)
    p.free()
    return
}

這是函數(shù)fmt.Fprint的實(shí)現(xiàn),它的功能就是實(shí)現(xiàn)把數(shù)據(jù)a寫(xiě)入到一個(gè)io.Writer接口,具體如何寫(xiě)入,它是不關(guān)心的。因?yàn)檫@都是io.Writer會(huì)做的,它只關(guān)心可以寫(xiě)入即可。w.Write(p.buf)調(diào)用Wirte方法寫(xiě)入。

最后的b.WriteTo(os.Stdout)是把最終的數(shù)據(jù)輸出到標(biāo)準(zhǔn)的os.Stdout里,以便我們查看輸出,它接收一個(gè)io.Writer接口類型的參數(shù)。開(kāi)篇我們講過(guò)os.Stdout也實(shí)現(xiàn)了這個(gè)io.Writer接口,所以就可以作為參數(shù)傳入。

這里我們會(huì)發(fā)現(xiàn),很多方法的接收參數(shù)都是io.Writer接口,當(dāng)然還有io.Reader接口,這就是面向接口的編程。我們不用關(guān)注具體實(shí)現(xiàn),只關(guān)注這個(gè)接口可以做什么事情。如果我們換成輸出到文件里,那也很容易,只需把os.File類型作為參數(shù)即可。任何實(shí)現(xiàn)了該接口的類型,都可以作為參數(shù)。

除了b.WriteTo方法外,我們還可以使用io.Reader接口的Read方法實(shí)現(xiàn)數(shù)據(jù)的讀取。

var p [100]byte
n,err:=b.Read(p[:])
fmt.Println(n,err,string(p[:n]))

這是最原始的方法,使用Read方法,n為讀取的字節(jié)數(shù),然后我們輸出打印出來(lái)。

因?yàn)閎yte.Buffer指針實(shí)現(xiàn)了io.Reader接口,所以我們還可以使用如下方式讀取數(shù)據(jù)信息。

data,err:=ioutil.ReadAll(&b)
fmt.Println(string(data),err)

ioutil.ReadAll接收了一個(gè)io.Reader接口的參數(shù),表明可以從任何實(shí)現(xiàn)了io.Reader接口的類型里讀取全部的數(shù)據(jù)。

func readAll(r io.Reader, capacity int64) (b []byte, err error) {
    buf := bytes.NewBuffer(make([]byte, 0, capacity))
    // If the buffer overflows, we will get bytes.ErrTooLarge.
    // Return that as an error. Any other panic remains.
    defer func() {
        e := recover()        
        if e == nil {
                    return
        }       
        if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
                   err = panicErr    
       } else {                    panic(e)        }    }()    _, err = buf.ReadFrom(r)    return buf.Bytes(), err
}

上述就是小編為大家分享的Go語(yǔ)言中Writer 和 Reader如何使用了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI