<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 中 io 包的使用方法

          共 23900字,需瀏覽 48分鐘

           ·

          2021-07-17 17:41

          Go 語言標準庫 io 包內(nèi)有一些常用接口和方法,本文配合圖片和實際代碼,詳細介紹了 io 包。

          前言

          在 Go 中,輸入和輸出操作是使用原語實現(xiàn)的,這些原語將數(shù)據(jù)模擬成可讀的或可寫的字節(jié)流。
          為此,Go 的 io 包提供了 io.Reader 和 io.Writer 接口,分別用于數(shù)據(jù)的輸入和輸出,如圖:

          Go 官方提供了一些 API,支持對內(nèi)存結(jié)構(gòu),文件,網(wǎng)絡(luò)連接等資源進行操作
          本文重點介紹如何實現(xiàn)標準庫中 io.Reader 和 io.Writer 兩個接口,來完成流式傳輸數(shù)據(jù)。

          io.Reader

          io.Reader 表示一個讀取器,它將數(shù)據(jù)從某個資源讀取到傳輸緩沖區(qū)。在緩沖區(qū)中,數(shù)據(jù)可以被流式傳輸和使用。

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

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

          Read() 方法有兩個返回值,一個是讀取到的字節(jié)數(shù),一個是發(fā)生錯誤時的錯誤。
          同時,如果資源內(nèi)容已全部讀取完畢,應該返回 io.EOF 錯誤。

          使用 Reader

          利用 Reader 可以很容易地進行流式數(shù)據(jù)傳輸。Reader 方法內(nèi)部是被循環(huán)調(diào)用的,每次迭代,它會從數(shù)據(jù)源讀取一塊數(shù)據(jù)放入緩沖區(qū) p (即 Read 的參數(shù) p)中,直到返回 io.EOF 錯誤時停止。

          下面是一個簡單的例子,通過 string.NewReader(string) 創(chuàng)建一個字符串讀取器,然后流式地按字節(jié)讀取:

          func main() {
              reader := strings.NewReader("Clear is better than clever")
              p := make([]byte4)

              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]))
              }
          }
          輸出打印的內(nèi)容:
          4 Clea
          4 r is
          4  bet
          4 ter 
          4 than
          4  cle
          3 ver
          EOF: 0 

          可以看到,最后一次返回的 n 值有可能小于緩沖區(qū)大小。

          自己實現(xiàn)一個 Reader

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

          type alphaReader struct {
              // 資源
              src string
              // 當前讀取到的位置 
              cur int
          }

          // 創(chuàng)建一個實例
          func newAlphaReader(src string) *alphaReader {
              return &alphaReader{src: src}
          }

          // 過濾函數(shù)
          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) {
              // 當前位置 >= 字符串長度 說明已經(jīng)讀取到結(jié)尾 返回 EOF
              if a.cur >= len(a.src) {
                  return 0, io.EOF
              }

              // x 是剩余未讀取的長度
              x := len(a.src) - a.cur
              n, bound := 00
              if x >= len(p) {
                  // 剩余長度超過緩沖區(qū)大小,說明本次可完全填滿緩沖區(qū)
                  bound = len(p)
              } else if x < len(p) {
                  // 剩余長度小于緩沖區(qū)大小,使用剩余長度輸出,緩沖區(qū)不補滿
                  bound = x
              }

              buf := make([]byte, bound)
              for n < bound {
                  // 每次讀取一個字節(jié),執(zhí)行過濾函數(shù)
                  if char := alpha(a.src[a.cur]); char != 0 {
                      buf[n] = char
                  }
                  n++
                  a.cur++
              }
              // 將處理后得到的 buf 內(nèi)容復制到 p 中
              copy(p, buf)
              return n, nil
          }

          func main() {
              reader := newAlphaReader("Hello! It's 9am, where is the sun?")
              p := make([]byte4)
              for {
                  n, err := reader.Read(p)
                  if err == io.EOF {
                      break
                  }
                  fmt.Print(string(p[:n]))
              }
              fmt.Println()
          }
          輸出打印的內(nèi)容:
          HelloItsamwhereisthesun

          組合多個 Reader,目的是重用和屏蔽下層實現(xiàn)的復雜度

          標準庫已經(jīng)實現(xiàn)了許多 Reader。
          使用一個 Reader 作為另一個 Reader 的實現(xiàn)是一種常見的用法。
          這樣做可以讓一個 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) {
              // 這行代碼調(diào)用的就是 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() {
              //  使用實現(xiàn)了標準庫 io.Reader 接口的 strings.Reader 作為實現(xiàn)
              reader := newAlphaReader(strings.NewReader("Hello! It's 9am, where is the sun?"))
              p := make([]byte4)
              for {
                  n, err := reader.Read(p)
                  if err == io.EOF {
                      break
                  }
                  fmt.Print(string(p[:n]))
              }
              fmt.Println()
          }

          這樣做的另一個優(yōu)點是 alphaReader 能夠從任何 Reader 實現(xiàn)中讀取。
          例如,以下代碼展示了 alphaReader 如何與 os.File 結(jié)合以過濾掉文件中的非字母字符:

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

          io.Writer

          io.Writer 表示一個編寫器,它從緩沖區(qū)讀取數(shù)據(jù),并將數(shù)據(jù)寫入目標資源。

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

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

          Write() 方法有兩個返回值,一個是寫入到目標資源的字節(jié)數(shù),一個是發(fā)生錯誤時的錯誤。

          使用 Writer

          標準庫提供了許多已經(jīng)實現(xiàn)了 io.Writer 的類型。
          下面是一個簡單的例子,它使用 bytes.Buffer 類型作為 io.Writer 將數(shù)據(jù)寫入內(nèi)存緩沖區(qū)。

          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())
          }
          輸出打印的內(nèi)容:
          Channels orchestrate mutexes serializeCgo is not GoErrors are valuesDon't panic

          自己實現(xiàn)一個 Writer

          下面我們來實現(xiàn)一個名為 chanWriter 的自定義 io.Writer ,它將其內(nèi)容作為字節(jié)序列寫入 channel 。

          type chanWriter struct {
              // ch 實際上就是目標資源
              ch chan byte
          }

          func newChanWriter() *chanWriter {
              return &chanWriter{make(chan byte1024)}
          }

          func (w *chanWriter) Chan() <-chan byte {
              return w.ch
          }

          func (w *chanWriter) Write(p []byte) (int, error) {
              n := 0
              // 遍歷輸入數(shù)據(jù),按字節(jié)寫入目標資源
              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,只需在函數(shù) main() 中調(diào)用 writer.Write()(在單獨的 goroutine 中)。
          因為 chanWriter 還實現(xiàn)了接口 io.Closer ,所以調(diào)用方法 writer.Close() 來正確地關(guān)閉 channel,以避免發(fā)生泄漏和死鎖。

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

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

          os.File

          類型 os.File 表示本地系統(tǒng)上的文件。它實現(xiàn)了 io.Reader 和 io.Writer ,因此可以在任何 io 上下文中使用。
          例如,下面的例子展示如何將連續(xù)的字符串切片直接寫入文件:

          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 類型實現(xiàn)了 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 也可以用作讀取器來從本地文件系統(tǒng)讀取文件的內(nèi)容。
          例如,下面的例子展示了如何讀取文件并打印其內(nèi)容:

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

              p := make([]byte4)
              for {
                  n, err := file.Read(p)
                  if err == io.EOF {
                      break
                  }
                  fmt.Print(string(p[:n]))
              }
          }

          標準輸入、輸出和錯誤

          os 包有三個可用變量 os.Stdout ,os.Stdin 和 os.Stderr ,它們的類型為 *os.File,分別代表 系統(tǒng)標準輸入,系統(tǒng)標準輸出 和 系統(tǒng)標準錯誤 的文件句柄。
          例如,下面的代碼直接打印到標準輸出:

          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 也實現(xiàn)了 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() 可以輕松地將數(shù)據(jù)從一個 Reader 拷貝到另一個 Writer。
          它抽象出 for 循環(huán)模式(我們上面已經(jīng)實現(xiàn)了)并正確處理 io.EOF 和 字節(jié)計數(shù)。
          下面是我們之前實現(xiàn)的簡化版本:

          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 讀取數(shù)據(jù)并寫入 file 的流程
              if _, err := io.Copy(file, proverbs); err != nil {
                  fmt.Println(err)
                  os.Exit(1)
              }
              fmt.Println("file created")
          }

          那么,我們也可以使用 io.Copy() 函數(shù)重寫從文件讀取并打印到標準輸出的先前程序,如下所示:

          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()

          此函數(shù)讓我們方便地將字符串類型寫入一個 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.PipeWriter 和 io.PipeReader 在內(nèi)存管道中模擬 io 操作。
          數(shù)據(jù)被寫入管道的一端,并使用單獨的 goroutine 在管道的另一端讀取。
          下面使用 io.Pipe() 創(chuàng)建管道的 reader 和 writer,然后將數(shù)據(jù)從 proverbs 緩沖區(qū)復制到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 中讀取數(shù)據(jù)并拷貝到標準輸出
              io.Copy(os.Stdout, piper)
              piper.Close()
          }

          緩沖區(qū) io

          標準庫中 bufio 包支持 緩沖區(qū) io 操作,可以輕松處理文本內(nèi)容。
          例如,以下程序逐行讀取文件的內(nèi)容,并以值 '\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 封裝了一些非常方便的功能
          例如,下面使用函數(shù) ReadFile 將文件內(nèi)容加載到 []byte 中。

          package main

          import (
            "io/ioutil"
             ...
          )

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

          總結(jié)

          本文介紹了如何使用 io.Reader 和 io.Writer 接口在程序中實現(xiàn)流式 IO。
          閱讀本文后,您應該能夠了解如何使用 io 包來實現(xiàn) 流式傳輸 IO 數(shù)據(jù)的程序。
          其中有一些例子,展示了如何創(chuàng)建自己的類型,并實現(xiàn)io.Reader 和 io.Writer 。

          這是一個簡單介紹性質(zhì)的文章,沒有擴展開來講。
          例如,我們沒有深入文件 IO,緩沖 IO,網(wǎng)絡(luò) IO 或格式化 IO(保存用于將來的寫入)。
          我希望這篇文章可以讓你了解 Go 語言中 流式 IO 的常見用法是什么。

          謝謝!

          轉(zhuǎn)自:ronniesong

          segmentfault.com/a/1190000015591319

          文章轉(zhuǎn)載:Go開發(fā)大全
          (版權(quán)歸原作者所有,侵刪)


          點擊下方“閱讀原文”查看更多

          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天干干| 免费日本黄色网址 | 国产精品无码Al刘亦菲 | 91五月婷婷华人网站 | 免费无码一级A片大黄在线观看 |