一文了解io包中的discard型別

2023-07-13 09:01:18

1. 引言

io.discard是Go語言標準庫提供一個結構體型別,其在丟棄不需要的資料場景下非常好用。本文我們將從io.discard 型別的基本定義出發,講述其基本使用和實現原理,接著簡單描述 io.discard 的使用場景,基於此完成對 io.discard 型別的介紹。

2. 介紹

2.1 基本定義

io.discard 是 Go語言提供的一個Writer,這個Writer 比較特殊,其不會做任何事情。它會將寫入的資料立即丟棄,不會做任何處理。其定義如下:

type discard struct{}
func (discard) Write(p []byte) (int, error) {}
func (discard) WriteString(s string) (int, error) {}
func (discard) ReadFrom(r Reader) (n int64, err error) {}

discard 結構體型別沒有定義任何欄位,同時還提供了Write ,ReadFromWriteString 方法,Write 方法和WriteString 方法分別接收位元組切片和字串,然後返回寫入的位元組數。

同時還實現了io.ReaderFrom 介面,這個是為了在使用 io.Copy 函數時,將資料從源複製到io.discard 時,避免不必要的操作。

從上面discard 的定義可以看起來,其不是一個公開型別的結構體型別,所以我們並不能建立結構體範例。事實上Go語言提供了一個io.discard 範例的預定義常數,我們直接使用,無需自己建立範例,定義如下:

var Discard Writer = discard{}

2.2 使用說明

下面通過一個丟棄網路連線中不再需要的資料的例子,來展示io.Discard 的使用,程式碼範例如下:

package main

import (
        "fmt"
        "io"
        "net"
        "os"
)

func discardData(conn net.Conn, bytesToDiscard int64) error {
        _, err := io.CopyN(io.Discard, conn, bytesToDiscard)
        return err
}

func main() {
        conn, err := net.Dial("tcp", "example.com:80")
        if err != nil {
                fmt.Println("連線錯誤:", err)
                return
        }
        defer conn.Close()

        bytesToDiscard := int64(1024) // 要丟棄的位元組數

        err = discardData(conn, bytesToDiscard)
        if err != nil {
                fmt.Println("丟棄資料錯誤:", err)
                return
        }

        fmt.Println("資料已成功丟棄。")
}

在上面範例中,我們建立了網路連線,然後連線中的前1024個位元組的資料是不需要的。這個時候,我們通過io.CopyN 函數將資料從conn 拷貝到io.Discard 當中,基於io.Discard 丟棄資料的特性,成功將連線的前1024個位元組丟棄掉,而不需要自定義緩衝區之類的操作,簡單高效。

3. 實現原理

io.Discard的目的是在某些場景下提供一個滿足io.Writer介面的範例,但使用者對於資料的寫入操作並不關心。它可以被用作一個黑洞般的寫入目標,默默地丟棄所有寫入它的資料。所以io.discard 的實現也相對比較簡單,不對輸入的資料進行任何處理即可,下面我們來看具體的實現。

首先是io.discard 結構體的定義,沒有定義任何欄位,因為本來也不需要執行任何寫入操作:

type discard struct{}

而對於WriteWriteString 方法,其直接返回了傳入引數的長度,往該Writer 寫入的資料不會被寫入到其他地方,而是被直接丟棄:

func (discard) Write(p []byte) (int, error) {
   return len(p), nil
}

func (discard) WriteString(s string) (int, error) {
   return len(s), nil
}

同時discard 也實現了io.ReaderFrom 介面,實現了ReadFrom 方法,實現也是非常簡單,從blackHolePool 緩衝池中獲取位元組切片,然後不斷讀取資料,讀取完成之後,再將位元組切片重新放入緩衝池當中:

// 存在一個位元組切片緩衝池
var blackHolePool = sync.Pool{
   New: func() any {
      b := make([]byte, 8192)
      return &b
   },
}

func (discard) ReadFrom(r Reader) (n int64, err error) {
   // 從緩衝池中取出一個 位元組切片
   bufp := blackHolePool.Get().(*[]byte)
   readSize := 0
   for {
      // 不斷讀取資料,bufp 只是作為一個讀取資料的中介,讀取到的資料並無意義
      readSize, err = r.Read(*bufp)
      n += int64(readSize)
      if err != nil {
         // 將位元組切片 重新放入到 blackHolePool 當中
         blackHolePool.Put(bufp)
         if err == EOF {
            return n, nil
         }
         return
      }
   }
}

io.Copy 函數中,將呼叫discard 中的ReadFrom 方法,能夠將Writer中的所有資料讀取完,然後丟棄掉。

4. 使用場景

io.Discard 給我們提供了一個io.Writer 介面的範例,同時其又不會真實得寫入資料,這個在某些場景下非常有用。

有時候,我們可能需要一個實現io.Writer 介面的範例,但是我們並不關心資料寫入Writer 的結果,也不關心資料是否寫到了哪個地方,此時io.Discard 就給我們提供了一個方便的解決方案。同時io.Discard 可以作為一個黑洞寫入目標,能夠將資料默默丟棄掉,不會進行實際的處理和儲存。

所以如果我們想要丟棄某些資料,亦或者是需要一個io.Writer介面的範例,但是對於寫入結果不需要關注時,此時使用io.Discard 是非常合適的。

5. 總結

io.discard 函數是Go語言標準庫中一個實現了Writer介面的結構體型別,能夠悄無聲息得實現資料的丟棄。 我們先從io.discard 型別的基本定義出發,之後通過一個簡單的範例,展示如何使用io.discard 型別實現對不需要資料的丟棄。

接著我們講述了io.discard 型別的實現原理,其實就是不對寫入的資料執行任何操作。在使用場景下,我們想要丟棄某些資料,亦或者是需要一個io.Writer介面的範例,但是對於寫入結果不需要關注時,此時使用io.Discard 是非常合適的。

基於此,便完成了對io.discard 型別的介紹,希望對你有所幫助。