Go36-40,41-io包中的接口和工具

字符操作和字節操作類型的接口

首先,瞭解一下strings.Builder、strings.Reader和bytes.Buffer這三個數據類型中實現的接口。

strings.Builder類型

strings.Builder類型主要用於構建字符串,它的指針類型實現的接口有:

  • io.Writer
  • io.ByteWriter
  • fmt.Stringer
  • io.stringWriter,io包的包級私有接口

strings.Reader類型

strings.Reader類型主要用於讀取字符串,它的指針類型實現的接口有:

  • io.Reader
  • io.ReaderAt
  • io.ByteReader
  • io.RuneReader
  • io.Seeker
  • io.ByteScanner,這個是io.ByteReader接口的擴展
  • io.RuneScanner, 這個是io.RuneReader接口的擴展
  • io.WriterTo

bytes.Buffer類型

bytes.Buffer是集讀、寫功能於一身的數據類型,它非常適合作爲字節序列的緩衝區。它的指針類型實現的接口非常多。
該指針類型實現的讀取相關的接口有:

  • io.Reader
  • io.ByteReader
  • io.RuneReader
  • io.ByteScanner
  • io.RuneScanner
  • io.WriterTo

該指針類型實現的寫入相關的接口有:

  • io.Writer
  • io.ByteWriter
  • io.stringWriter,io包的包級私有接口
  • io.ReaderFrom

另外,還有一個導出相關的接口:fmt.Stringer

驗證代碼

下面的代碼對公開的接口進行了驗證:

package main

import (
    "bytes"
    "fmt"
    "io"
    "strings"
)

func main() {
    b1 := new(strings.Builder)
    _ = interface{}(b1).(io.Writer)
    _ = interface{}(b1).(io.ByteWriter)
    _ = interface{}(b1).(fmt.Stringer)

    b2 := strings.NewReader("")
    _ = interface{}(b2).(io.Reader)
    _ = interface{}(b2).(io.ReaderAt)
    _ = interface{}(b2).(io.ByteReader)
    _ = interface{}(b2).(io.RuneReader)
    _ = interface{}(b2).(io.Seeker)
    _ = interface{}(b2).(io.ByteScanner)
    _ = interface{}(b2).(io.RuneScanner)
    _ = interface{}(b2).(io.WriterTo)

    b3 := bytes.NewBuffer([]byte{})
    _ = interface{}(b3).(io.Reader)
    _ = interface{}(b3).(io.ByteReader)
    _ = interface{}(b3).(io.RuneReader)
    _ = interface{}(b3).(io.ByteScanner)
    _ = interface{}(b3).(io.RuneScanner)
    _ = interface{}(b3).(io.WriterTo)

    _ = interface{}(b3).(io.Writer)
    _ = interface{}(b3).(io.ByteWriter)
    _ = interface{}(b3).(io.ReaderFrom)

    _ = interface{}(b3).(fmt.Stringer)
}

io包

上面的這些類型實現了這麼多的接口,目的是爲了提高不同程序實體之間的互操作性。
在io包中,有如下幾個用於拷貝數據的函數:

  • io.Copy
  • io.CopyBuffer
  • io.CopyN

這幾個函數在功能上略有差別,但是首先都會接收2個參數:

  • dst,io.Writer類型,表示數據目的
  • src,io.Reader類型,表示數據來源

而這些函數的功能大致上也是把數據從src拷貝到dst。用了接口之後,不論給予參數值是什麼類型的,只要實現了接口就行。只要實現了接口,這些函數幾乎就可以正常執行了。當然,在函數中還會對必要的參數值進行有效性的檢查,如果檢查不通過,它的執行也是不能夠成功結束的。

io.CopyN函數舉例

來看下面的示例代碼:

package main

import (
    "fmt"
    "io"
    "os"
    "strings"
)

func main() {
    src := strings.NewReader("Happy New Year")
    dst := new(strings.Builder)
    written, err := io.CopyN(dst, src, 5)
    if err != nil {
        fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
    }
    fmt.Println(written, dst.String())
}

首先,使用了strings.NewReader創建了一個字符串讀取器,並把它賦值給了變量src,然後有new了一個字符串構建器,並將其賦予了變量dst。
之後,調用了io.CopyN函數的時候,把兩個變量的值都傳遞了進去,同時還指定了第三個參數int64類型,就是要從src中拷貝多少個字節到dst裏。
雖然,變量src和dst類型分別是strings.Reader和strings.Builder,但是當它們被傳到io.CopyN函數的時候,就已經分別被包裝成了io.Reader類型和io.Writer類型的值。而io.CopyN函數也根本不會去在意它們的實際類型到底是什麼。爲了優化的目的,io.CopyN函數中的代碼會對參數值進行再包裝,也會檢測這些參數值是否還實現了別的接口,甚至還會去探求某個參數值被包裝後的實際類型,是否未某個特殊的類型。但是總體上來看,這些代碼都是面向參數聲明中的接口來做的。

面向接口編程
在上面的示例中,通過面向接口編程,極大地拓展了它的適用範圍和應用場景。換個角度來看,正式因爲strings.Reader類型和strings.Builder類型都實現了不少接口,所以他們的值才能夠被使用在更廣闊的場景中。比如strings包和bytes包中的數據類型在實現了若干接口之後得到了很多好處,這就是面向接口編程帶來的優勢。
在Go語言中,對接口的擴展是通過類型之間的嵌入來實現的,這也常被叫做接口的組合。這個在講接口的時候也提過,Go語言提倡使用小接口加接口組合的方式,來擴展程序的行爲以及增加程序的靈活性。io代碼包恰恰就可以作爲這樣的一個標杆,它可以成爲我們運用這種技巧是的一個參考標準。

接口擴展和實現

以io.Reader接口爲對象,來了解一下接口擴展和實現,以及各自的功用。
在io包中,io.Reader的擴展接口有下面幾種:

  • io.ReadWriter,既是io.Reader的擴展接口,也是io.Writer的擴展接口。該接口定了以一組行爲,包含且僅包含了基本的字節序列讀取方法Read,和字節序列寫入方法Write。
  • io.ReadCloser,io.Reader接口和io.Closer接口的組合。除了包含基本的字節序列讀取方法之外,還擁有一個基本的關閉方法Close。關閉方法一般用於關閉數據讀寫的通路。
  • io.ReadWriteCloser,很明顯,就是io.Reader、io.Writer和io.Closer這三個接口的組合。
  • io.ReadSeeker,此接口的特點就是擁有一個用於尋找讀寫位置的基本方法Seek。該方法可以根據給定的偏移量基於數據的起始位置、末尾位置,或者當前讀寫位置去尋找新的讀寫位置。Seek是io.Seeker接口唯一的一個方法。
  • io.ReadWriteSeeker,顯然,就是io.Reader、io.Writer和io.Seeker這三個接口的組合。

然後是io包中的io.Reader接口的實現類型,包括以下幾項內容:

  • *io.LimitedReader
  • *io.SectionReader
  • *io.teeReader
  • *io.multiReader
  • *io.pipe
  • *io.PipeReader

這裏忽略掉了測試源碼文件中的實現類型,以及不會以任何形式直接對外暴露的那些實現類型。

*io.LimitedReader
結構體類型如下:

type LimitedReader struct {
    R Reader // underlying reader
    N int64  // max bytes remaining
}

此類型的基本類型會包裝io.Reader類型的值,並提供一個額外的受限讀取的功能。所謂的受限讀取指的是,此類型的讀取方法和Read返回的總數據量會受到限制,無論該方法被調用多少次。這個限制由該類型的字段N表明,單位是字節。

*io.SectionReader
結構體類型如下:

type SectionReader struct {
    r     ReaderAt
    base  int64
    off   int64
    limit int64
}

此類型的基本類型會包裝io.ReaderAt類型的值,並且會限制它的Read方法,只能夠讀取原始數據中的某一個部分,或者說一段。這個數據段的起始位置和末尾位置,需要在它被初始化的時候就指明,並且之後無法變更。該類型值的行爲與切片有些類似,它只會對外暴露在其窗口之中的那些數據。

*io.teeReader
結構體類型如下:

type teeReader struct {
    r Reader
    w Writer
}

func TeeReader(r Reader, w Writer) Reader {
    return &teeReader{r, w}
}

此類型是一個包級私有的數據類型,也是io.TeeReader函數結果值的實際類型,這個函數接受兩個參數r和w。*teeReader的Read方法會把r中的數據經過作爲方法參數的字節切片p寫入到w。可以說,這是一個r和w之間的數據橋樑,而那個參數p就是這座橋上的數據搬運者。

*io.multiReader
結構體類型如下:

type multiWriter struct {
    writers []Writer
}

func MultiReader(readers ...Reader) Reader {
    r := make([]Reader, len(readers))
    copy(r, readers)
    return &multiReader{r}
}

此類型也是一個包級私有的數據類型。通過io.MultiReader函數,接受若干個io.Reader類型的參數值,返回一個實例類型爲*io.multiWriter的結果值。它的Read方法被調用時,會順序的從前面的那些io.Reader類型的參數值中讀取數據。因此,也可以稱之爲多對象讀取器

*io.pipe
結構體類型如下:

type pipe struct {
    wrMu sync.Mutex // Serializes Write operations
    wrCh chan []byte
    rdCh chan int

    once sync.Once // Protects closing done
    done chan struct{}
    rerr atomicError
    werr atomicError
}

func Pipe() (*PipeReader, *PipeWriter) {
    p := &pipe{
        wrCh: make(chan []byte),
        rdCh: make(chan int),
        done: make(chan struct{}),
    }
    return &PipeReader{p}, &PipeWriter{p}
}

此類型爲一個包級私有的數據類型,它比較複雜。不但實現了io.Reader接口,而且還實現了io.Writer接口。io.PipeReader類型和io.PipeWriter類型擁有的所有指針方法都是以它爲基礎的。這些方法都只是代理了io.pipe類型值所擁有的某一個方法而已。又因爲,io.Pipe函數會返回這兩個類型的指針值並分別把它們作爲其生成的同步內存管理的兩端,所以*io.pipe類型就是io包提供的同步內存管道的核心實現。

*io.PipeReader
結構體類型如下:

type PipeReader struct {
    p *pipe
}

此類型可以被視爲io.pipe類型的代理類型。它代理了io.pipe中一部分功能,並基於io.pipe實現了io.ReadCloser接口。同時,它還定義了同步內存管道的讀取端。

集中示例展示

上面所講的每一個類型都寫了一小段代碼,展示了這些類型的一些基本用法:

package main

import (
    "fmt"
    "io"
    "os"
    "strings"
    "sync"
)

// 統一定義一個方法來處理錯誤,這樣不會看到很多 if err != nil {} 這種
func executeIfNoErr(err error, f func()) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "\tERROR: %v\n", err)
        return
    }
    f()
}

func main() {
    comment := "Make the plan. " +
        "Execute the plan. " +
        "Expect the plan to go off the rails. " +
        "Throw away the plan."

    fmt.Println("原生string類型:")
    reader1 := strings.NewReader(comment)
    buf1 := make([]byte, 4)
    n, err := reader1.Read(buf1)
    var offset1, index1 int64
    executeIfNoErr(err, func() {
        fmt.Printf("\tRead(%d): %q\n", n, buf1[:n])
        offset1 = int64(5)
        index1, err = reader1.Seek(offset1, io.SeekCurrent)
    })
    executeIfNoErr(err, func() {
        fmt.Printf("\t偏移量: %d,  %d\n", offset1, index1)
        n, err = reader1.Read(buf1)
    })
    executeIfNoErr(err, func() {
        fmt.Printf("\tRead(%d): %q\n", n, buf1[:n])
    })

    reader1.Reset(comment)
    num2 := int64(15)
    fmt.Printf("LimitReader類型,限制數據量(%d):\n", num2)
    reader2 := io.LimitReader(reader1, num2)
    buf2 := make([]byte, 4)
    for i := 0; i < 6; i++ {
        n, err := reader2.Read(buf2)
        executeIfNoErr(err, func() {
            fmt.Printf("\tRead(%d): %q\n", n, buf2[:n])
        })
    }

    reader1.Reset(comment)
    offset3 := int64(33)
    num3 := int64(37)
    fmt.Printf("SectionReader類型,起始偏移量(%d),到末端的長度(%d):\n", offset3, num3)
    reader3 := io.NewSectionReader(reader1, offset3, num3)
    buf3 := make([]byte, 15)
    for i := 0; i < 5; i++ {
        n, err := reader3.Read(buf3)
        executeIfNoErr(err, func() {
            fmt.Printf("\tRead(%d): %q\n", n, buf3[:n])
        })
    }

    reader1.Reset(comment)
    writer4 := new(strings.Builder)
    fmt.Printf("teeReader類型,write4現在應該爲空(%q):\n", writer4)
    reader4 := io.TeeReader(reader1, writer4)
    buf4 := make([]byte, 33)
    for i := 0; i < 5; i++ {
        n, err := reader4.Read(buf4)
        executeIfNoErr(err, func() {
            fmt.Printf("\tRead(%d): %q\n", n, buf4[:n])
            fmt.Printf("\tWrite: %q\n", writer4)
        })
    }

    reader5a := strings.NewReader("Make the plan.")
    reader5b := strings.NewReader("Execute the plan.")
    reader5c := strings.NewReader("Expect the plan to go off the rails.")
    reader5d := strings.NewReader("Throw away the plan.")
    fmt.Println("multiWriter類型,一共4個readers:")
    reader5 := io.MultiReader(reader5a, reader5b, reader5c, reader5d)
    buf5 := make([]byte, 15)
    for i := 0; i < 10; i++ {
        n, err := reader5.Read(buf5)
        executeIfNoErr(err, func() {
            fmt.Printf("\tRead(%d): %q\n", n, buf5[:n])
        })
    }

    fmt.Println("pipe類型:")
    pReader, pWriter := io.Pipe()
    _ = interface{}(pReader).(io.ReadCloser) // 驗證是否實現了 io.ReadCloser 接口
    _ = interface{}(pWriter).(io.WriteCloser)
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        n, err := pWriter.Write([]byte(comment))
        defer pWriter.Close()
        executeIfNoErr(err, func() {
            fmt.Printf("\tWrite(%d)\n", n)
        })
    }()
    go func() {
        defer wg.Done()
        buf6 := make([]byte, 15)
        for i := 0; i < 10; i++ {
            n, err := pReader.Read(buf6)
            executeIfNoErr(err, func() {
                fmt.Printf("\tRead(%d): %q\n", n, buf6[:n])
            })
        }
    }()
    wg.Wait()

    fmt.Println("所有示例完成")
}

io包中的接口

前面的內容,主要講的是io.Reader的擴展接口和實現類型。當然,io代碼包中的核心接口不止io.Reader一個。這裏基於它引出的一條主線只是io包類型體系中的一部分。這裏再換個角度來對io包做進一步的瞭解。
可以把沒有嵌入其他接口並且只定義了一個方法的接口叫做簡單接口。在io包中,這樣的接口共有11個。
另外,有的接口有着衆多的擴展接口和實現類型,可以稱爲核心接口,io包中的核心接口只有3個:

  • io.Reader
  • io.Writer
  • io.Closer

可以把io包中的簡單接口分爲四大類。這四大類接口分別針對於四種操作:讀取、寫入、關閉和讀寫位置設定。前三種操作屬於基本的I/O操作。

關於讀取操作,已經重點講過核心接口的io.Reader。它在io包中有5個擴展接口,並有6個實現類型。這個包中針對讀取操作的接口還有不少。

io.ByteReader

type ByteReader interface {
    ReadByte() (byte, error)
}

簡單接口,定義了一個讀取方法ReadByte。這個讀取方法能夠讀取下一個單一的字節。

RuneReader

type RuneReader interface {
    ReadRune() (r rune, size int, err error)
}

簡單接口,定義了一個讀取方法ReadRune。這個讀取方法能夠讀取下一個單一的Unicode字符。

io.ByteScanner

type ByteScanner interface {
    ByteReader
    UnreadByte() error
}

該接口內嵌了簡單接口io.ByteReader,並定義了額外的UnreadByte方法。它就抽象了可以讀取和讀回退單個字節的功能集。

io.RuneScanner

type RuneScanner interface {
    RuneReader
    UnreadRune() error
}

該接口內嵌了簡單接口io.RuneReader,並定義了額外的UnreadRune方法。它抽象了可以讀取和讀回退單個Unicode字符的功能集。

io.ReaderAt

type ReaderAt interface {
    ReadAt(p []byte, off int64) (n int, err error)
}

簡單接口,定義了一個ReadAt方法。這是一個純粹的只讀方法,它只去讀取其所屬值中包含的字節,而不對這個值進行任何改動。比如,它絕對不能去修改已讀計數的值。這也是io.ReaderAt接口與其實現類型之間最重要的一個約定。因此,如果僅僅併發的調用某一個值的ReadAt方法,那麼安全性應該是可以得到保障的。

io.WriterTo

type WriterTo interface {
    WriteTo(w Writer) (n int64, err error)
}

簡單接口,定義了一個WriteTo的讀取方法。該方法接受一個io.Writer類型的參數值,會把其所屬值中的數據讀出,並寫入到這個參數值中。

io.ReaderFrom

type ReaderFrom interface {
    ReadFrom(r Reader) (n int64, err error)
}

簡單接口,定義了一個ReadFrom的寫入方法。該方法接受一個io.Reader類型的參數值,會從該參數值中讀取出數據,並寫入到其所屬值中。

寫入操作相關接口

從上面這些接口中,可以看出,在io包中與寫入操作有關的接口都與讀取操作相關的接口有着一定的對應關係。下面就說是寫入操作有關的接口。

io.Write

io.Write是核心接口。基於它的擴展接口如下:

  • io.ReadWriter,實現類型有*io.pipe
  • io.ReadWriteCloser
  • io.ReadWriteSeeker,在io包中沒有這個接口的實現,它的實現類型主要集中在net包中。
  • io.WriteCloser
  • io.WriteSeker

io.ByteWriter和io.WriterAt

這兩個是寫入操作相關的簡單接口。在io包中,沒有他們的實現類型。
順便提一下這個數據類型:*io.File。這個類型不但是io.WriterAt接口的實現類型,同時還實現了io.ReadWriteCloser接口和io.ReadWriteSeeker接口。就是說,該類型支持的I/O操作非常豐富。

io.Seeker

這個接口是一個讀寫位置設定相關的簡單接口,也僅僅定義了一個Seek方法。該方法主要用於尋找並設定下一次讀取或寫入時的起始索引位置,在strings包裏講過。
在io包中,有幾個基於io.Seeker的擴展接口:

  • io.ReadSeeker
  • io.ReadWriteSeeker
  • io.WriteSeeker,基於io.Writer和io.Seeker的擴展接口

這兩個類型的指針:strings.Reader和io.SectionReader,都實現了io.Seeker接口。順便提一下,這兩個類型的指針也都是io.ReaderAt接口的實現類型。

io.Closer

這是關閉操作相關的接口,非常通用,它的擴展接口和實現類型都不少。單從名稱上就能看出io包中哪些接口是它的擴展接口。它的實現類型,在io包中只有io.PipeReader和io.PipeWriter。

總結

本篇是爲了能夠使我們牢記io包中有着網狀關係的接口和數據類型。如果暫時未能牢記,至少可以作爲深刻記憶它們的開始。
在之後需要思考和時間的是:在什麼時候應該編寫哪些數據類型實現io包中的哪些接口,並以此得到最大的好處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章