Go 中 io 包的使用方法

來源:互聯網
上載者:User

前言

在 Go 中,輸入和輸出操作是使用原語實現的,這些原語將資料類比成可讀的或可寫的位元組流。
為此,Go 的 io 包提供了 io.Readerio.Writer 介面,分別用於資料的輸入和輸出,

Go 官方提供了一些 API,支援對記憶體結構檔案網路連接等資源進行操作
本文重點介紹如何?標準庫中 io.Readerio.Writer 兩個介面,來完成串流資料。

io.Reader

io.Reader 表示一個讀取器,它將資料從某個資源讀取到傳輸緩衝區。在緩衝區中,資料可以被串流和使用。

對於要用作讀取器的類型,它必須實現 io.Reader 介面的唯一一個方法 Read(p []byte)
換句話說,只要實現了 Read(p []byte) ,那它就是一個讀取器。

type Reader interface {    Read(p []byte) (n int, err error)}

Read() 方法有兩個傳回值,一個是讀取到的位元組數,一個是發生錯誤時的錯誤。
同時,如果資源內容已全部讀取完畢,應該返回 io.EOF 錯誤。

使用 Reader

利用 Reader 可以很容易地進行流式資料轉送。Reader 方法內部是被迴圈調用的,每次迭代,它會從資料來源讀取一塊資料放入緩衝區 p (即 Read 的參數 p)中,直到返回 io.EOF 錯誤時停止。

下面是一個簡單的例子,通過 string.NewReader(string) 建立一個字串讀取器,然後流式地按位元組讀取:

func main() {    reader := strings.NewReader("Clear is better than clever")    p := make([]byte, 4)    for {        n, err := reader.Read(p)        if err != nil{            if err == io.EOF {                fmt.Println("EOF:", n)                break            }            fmt.Println(err)            os.Exit(1)        }        fmt.Println(n, string(p[:n]))    }}
輸出列印的內容:4 Clea4 r is4  bet4 ter 4 than4  cle3 verEOF: 0 

可以看到,最後一次返回的 n 值有可能小於緩衝區大小。

自己實現一個 Reader

上一節是使用標準庫中的 io.Reader 讀取器實現的。
現在,讓我們看看如何自己實現一個。它的功能是從流中過濾掉非字母字元。

type alphaReader struct {    // 資源    src string    // 當前讀取到的位置     cur int}// 建立一個執行個體func newAlphaReader(src string) *alphaReader {    return &alphaReader{src: src}}// 過濾函數func alpha(r byte) byte {    if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') {        return r    }    return 0}// Read 方法func (a *alphaReader) Read(p []byte) (int, error) {    // 當前位置 >= 字串長度 說明已經讀取到結尾 返回 EOF    if a.cur >= len(a.src) {        return 0, io.EOF    }    // x 是剩餘未讀取的長度    x := len(a.src) - a.cur    n, bound := 0, 0    if x >= len(p) {        // 剩餘長度超過緩衝區大小,說明本次可完全填滿緩衝區        bound = len(p)    } else if x < len(p) {        // 剩餘長度小於緩衝區大小,使用剩餘長度輸出,緩衝區不補滿        bound = x    }    buf := make([]byte, bound)    for n < bound {        // 每次讀取一個位元組,執行過濾函數        if char := alpha(a.src[a.cur]); char != 0 {            buf[n] = char        }        n++        a.cur++    }    // 將處理後得到的 buf 內容複寫到 p 中    copy(p, buf)    return n, nil}func main() {    reader := newAlphaReader("Hello! It's 9am, where is the sun?")    p := make([]byte, 4)    for {        n, err := reader.Read(p)        if err == io.EOF {            break        }        fmt.Print(string(p[:n]))    }    fmt.Println()}
輸出列印的內容:HelloItsamwhereisthesun

組合多個 Reader,目的是重用和屏蔽下層實現的複雜度

標準庫已經實現了許多 Reader
使用一個 Reader 作為另一個 Reader 的實現是一種常見的用法。
這樣做可以讓一個 Reader 重用另一個 Reader 的邏輯,下面展示通過更新 alphaReader 以接受 io.Reader 作為其來源。

type alphaReader struct {    // alphaReader 裡組合了標準庫的 io.Reader    reader io.Reader}func newAlphaReader(reader io.Reader) *alphaReader {    return &alphaReader{reader: reader}}func alpha(r byte) byte {    if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') {        return r    }    return 0}func (a *alphaReader) Read(p []byte) (int, error) {    // 這行代碼調用的就是 io.Reader    n, err := a.reader.Read(p)    if err != nil {        return n, err    }    buf := make([]byte, n)    for i := 0; i < n; i++ {        if char := alpha(p[i]); char != 0 {            buf[i] = char        }    }    copy(p, buf)    return n, nil}func main() {    //  使用實現了標準庫 io.Reader 介面的 strings.Reader 作為實現    reader := newAlphaReader(strings.NewReader("Hello! It's 9am, where is the sun?"))    p := make([]byte, 4)    for {        n, err := reader.Read(p)        if err == io.EOF {            break        }        fmt.Print(string(p[:n]))    }    fmt.Println()}

這樣做的另一個優點是 alphaReader 能夠從任何 Reader 實現中讀取。
例如,以下代碼展示了 alphaReader 如何與 os.File 結合以過濾掉檔案中的非字母字元:

func main() {    // file 也實現了 io.Reader    file, err := os.Open("./alpha_reader3.go")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    defer file.Close()        // 任何實現了 io.Reader 的類型都可以傳入 newAlphaReader    // 至於具體如何讀取檔案,那是標準庫已經實現了的,我們不用再做一遍,達到了重用的目的    reader := newAlphaReader(file)    p := make([]byte, 4)    for {        n, err := reader.Read(p)        if err == io.EOF {            break        }        fmt.Print(string(p[:n]))    }    fmt.Println()}

io.Writer

io.Writer 表示一個編寫器,它從緩衝區讀取資料,並將資料寫入目標資源。

對於要用作編寫器的類型,必須實現 io.Writer 介面的唯一一個方法 Write(p []byte)
同樣,只要實現了 Write(p []byte) ,那它就是一個編寫器。

type Writer interface {    Write(p []byte) (n int, err error)}

Write() 方法有兩個傳回值,一個是寫入到目標資源的位元組數,一個是發生錯誤時的錯誤。

使用 Writer

標準庫提供了許多已經實現了 io.Writer 的類型。
下面是一個簡單的例子,它使用 bytes.Buffer 類型作為 io.Writer 將資料寫入記憶體緩衝區。

func main() {    proverbs := []string{        "Channels orchestrate mutexes serialize",        "Cgo is not Go",        "Errors are values",        "Don't panic",    }    var writer bytes.Buffer    for _, p := range proverbs {        n, err := writer.Write([]byte(p))        if err != nil {            fmt.Println(err)            os.Exit(1)        }        if n != len(p) {            fmt.Println("failed to write data")            os.Exit(1)        }    }    fmt.Println(writer.String())}
輸出列印的內容:Channels orchestrate mutexes serializeCgo is not GoErrors are valuesDon't panic

自己實現一個 Writer

下面我們來實現一個名為 chanWriter 的自訂 io.Writer ,它將其內容作為位元組序列寫入 channel

type chanWriter struct {    // ch 實際上就是目標資源    ch chan byte}func newChanWriter() *chanWriter {    return &chanWriter{make(chan byte, 1024)}}func (w *chanWriter) Chan() <-chan byte {    return w.ch}func (w *chanWriter) Write(p []byte) (int, error) {    n := 0    // 遍曆輸入資料,按位元組寫入目標資源    for _, b := range p {        w.ch <- b        n++    }    return n, nil}func (w *chanWriter) Close() error {    close(w.ch)    return nil}func main() {    writer := newChanWriter()    go func() {        defer writer.Close()        writer.Write([]byte("Stream "))        writer.Write([]byte("me!"))    }()    for c := range writer.Chan() {        fmt.Printf("%c", c)    }    fmt.Println()}

要使用這個 Writer,只需在函數 main() 中調用 writer.Write()(在單獨的goroutine中)。
因為 chanWriter 還實現了介面 io.Closer ,所以調用方法 writer.Close() 來正確地關閉channel,以避免發生泄漏和死結。

io 包裡其他有用的類型和方法

如前所述,Go標準庫附帶了許多有用的功能和類型,讓我媽可以便於使用流式io。

os.File

類型 os.File 表示本地系統上的檔案。它實現了 io.Readerio.Writer ,因此可以在任何 io 上下文中使用。
例如,下面的例子展示如何將連續的字串切片直接寫入檔案:

func main() {    proverbs := []string{        "Channels orchestrate mutexes serialize\n",        "Cgo is not Go\n",        "Errors are values\n",        "Don't panic\n",    }    file, err := os.Create("./proverbs.txt")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    defer file.Close()    for _, p := range proverbs {        // file 類型實現了 io.Writer        n, err := file.Write([]byte(p))        if err != nil {            fmt.Println(err)            os.Exit(1)        }        if n != len(p) {            fmt.Println("failed to write data")            os.Exit(1)        }    }    fmt.Println("file write done")}

同時,io.File 也可以用作讀取器來從本地檔案系統讀取檔案的內容。
例如,下面的例子展示了如何讀取檔案並列印其內容:

func main() {    file, err := os.Open("./proverbs.txt")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    defer file.Close()    p := make([]byte, 4)    for {        n, err := file.Read(p)        if err == io.EOF {            break        }        fmt.Print(string(p[:n]))    }}

標準輸入、輸入和錯誤

os 包有三個可用變數 os.Stdoutos.Stdinos.Stderr ,它們的類型為 *os.File,分別代表 系統標準輸入系統標準輸出系統標準錯誤 的檔案控制代碼。
例如,下面的代碼直接列印到標準輸出:

func main() {    proverbs := []string{        "Channels orchestrate mutexes serialize\n",        "Cgo is not Go\n",        "Errors are values\n",        "Don't panic\n",    }    for _, p := range proverbs {        // 因為 os.Stdout 也實現了 io.Writer        n, err := os.Stdout.Write([]byte(p))        if err != nil {            fmt.Println(err)            os.Exit(1)        }        if n != len(p) {            fmt.Println("failed to write data")            os.Exit(1)        }    }}

io.Copy()

io.Copy() 可以輕鬆地將資料從一個 Reader 拷貝到另一個 Writer。
它抽象出 for 迴圈模式(我們上面已經實現了)並正確處理 io.EOF 和 位元組計數。
下面是我們之前實現的簡化版本:

func main() {    proverbs := new(bytes.Buffer)    proverbs.WriteString("Channels orchestrate mutexes serialize\n")    proverbs.WriteString("Cgo is not Go\n")    proverbs.WriteString("Errors are values\n")    proverbs.WriteString("Don't panic\n")    file, err := os.Create("./proverbs.txt")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    defer file.Close()    // io.Copy 完成了從 proverbs 讀取資料並寫入 file 的流程    if _, err := io.Copy(file, proverbs); err != nil {        fmt.Println(err)        os.Exit(1)    }    fmt.Println("file created")}

那麼,我們也可以使用 io.Copy() 函數重寫從檔案讀取並列印到標準輸出的先前程式,如下所示:

func main() {    file, err := os.Open("./proverbs.txt")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    defer file.Close()    if _, err := io.Copy(os.Stdout, file); err != nil {        fmt.Println(err)        os.Exit(1)    }}

io.WriteString()

此函數讓我們方便地將字串類型寫入一個 Writer:

func main() {    file, err := os.Create("./magic_msg.txt")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    defer file.Close()    if _, err := io.WriteString(file, "Go is fun!"); err != nil {        fmt.Println(err)        os.Exit(1)    }}

使用管道的 Writer 和 Reader

類型 io.PipeWriterio.PipeReader 在記憶體管道中類比 io 操作。
資料被寫入管道的一端,並使用單獨的 goroutine 在管道的另一端讀取。
下面使用 io.Pipe() 建立管道的 reader 和 writer,然後將資料從 proverbs 緩衝區複製到io.Stdout

func main() {    proverbs := new(bytes.Buffer)    proverbs.WriteString("Channels orchestrate mutexes serialize\n")    proverbs.WriteString("Cgo is not Go\n")    proverbs.WriteString("Errors are values\n")    proverbs.WriteString("Don't panic\n")    piper, pipew := io.Pipe()    // 將 proverbs 寫入 pipew 這一端    go func() {        defer pipew.Close()        io.Copy(pipew, proverbs)    }()    // 從另一端 piper 中讀取資料並拷貝到標準輸出    io.Copy(os.Stdout, piper)    piper.Close()}

緩衝區 io

標準庫中 bufio 包支援 緩衝區 io 操作,可以輕鬆處理常值內容。
例如,以下程式逐行讀取檔案的內容,並以值 '\n' 分隔:

func main() {    file, err := os.Open("./planets.txt")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    defer file.Close()    reader := bufio.NewReader(file)    for {        line, err := reader.ReadString('\n')        if err != nil {            if err == io.EOF {                break            } else {                fmt.Println(err)                os.Exit(1)            }        }        fmt.Print(line)    }}

ioutil

io 包下面的一個子包 utilio 封裝了一些非常方便的功能
例如,下面使用函數 ReadFile 將檔案內容載入到 []byte 中。

package mainimport (  "io/ioutil"   ...)func main() {    bytes, err := ioutil.ReadFile("./planets.txt")    if err != nil {        fmt.Println(err)        os.Exit(1)    }    fmt.Printf("%s", bytes)}

總結

本文介紹了如何使用 io.Readerio.Writer 介面在程式中實現流式IO。
閱讀本文後,您應該能夠瞭解如何使用 io 包來實現 串流IO資料的程式。
其中有一些例子,展示了如何建立自己的類型,並實現io.Readerio.Writer

這是一個簡單介紹性質的文章,沒有擴充開來講。
例如,我們沒有深入檔案IO,緩衝IO,網路IO或格式化IO(儲存用於將來的寫入)。
我希望這篇文章可以讓你瞭解 Go語言中 流式IO 的常見用法是什麼。

謝謝!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.