讀寫操做(補充)

讀寫操做(補充)

以前的一篇有關終端讀寫的內容比較詳細:
https://blog.51cto.com/steed/2315597服務器

這篇,先補充了兩個從終端逐行掃描並處理的示例。而後再補充了有關兩個寫入操做的內容。ide

bufio包,逐行讀取

關於bufio包,使用它能夠簡便和高效地處理輸入和輸出。其中一個最有用的特性是稱爲掃描器(Scanner)的類型,它能夠讀取輸入,以行或者單詞爲單位斷開,這是處理以行爲單位輸入內容的最簡單方式。
先聲明一個 bufio.Scanner 類型的變量:函數

input := bufio.NewScanner(os.Stdin)

掃描器從程序的標準輸入進行讀取。每一次調用 inout.Scan() 讀取下一行,而且將結尾的換行符去掉;經過調用 input.Text() 來獲取讀到的內容。Scan 方法在讀到新行的時候返回true,在沒有更多內容的時候返回flase。
下面的例子,從標準輸入獲取到內容後,轉成全大寫再打印出來。Windows下使用 Ctrl+Z 後回車能夠退出:工具

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    input := bufio.NewScanner(os.Stdin)
    for input.Scan() {
        fmt.Println(strings.ToUpper(input.Text()))
    }
}

上面的示例是每從標準輸入獲取到一行的內容,就進行處理。還有一種作法是,從標準輸入獲取所有的內容後(Ctrl+Z 後回車表示輸入完成),最後再一次所有輸出。下面的示例是從標準輸入獲取到所有內容,而後所有輸出,每一行的內容之間插入一個空格。最後也會有一個空格,輸出時把最後一個空格截斷:性能

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "os"
)

func main() {
    input := bufio.NewScanner(os.Stdin)
    buff := bytes.NewBuffer(nil)
    for input.Scan() {
        buff.Write(input.Bytes()) // 忽略錯誤
        buff.WriteByte(' ')
    }
    fmt.Printf("%q\n", buff.String()[:buff.Len()-1])  // 截掉最後一個空格
}

讀取內容並丟棄

使用io.Copy函數讀取響應的內容,好比直接複製內容到標準輸出,這樣就不須要把數據流裝到緩衝區:code

n, err := io.Copy(os.Stdout, resp.Body)

還能夠經過寫入 ioutil.Discard 輸出流進行丟棄,這樣作應該是爲了要有一個讀取的過程:blog

n, err := io.Copy(ioutil.Discard, resp.Body)

向 io.Writer 寫入字符串

這裏要講的是經過接口類型斷言來查詢特性,下面是一個標準庫中使用的示例。而且,這個是向 io.Writer 寫入字符串的推薦方法。
下面定義一個方法,往 io.Writer 接口接入字符串信息:接口

func writeMsg(w io.Writer, msg string) error {
    if _, err := w.Write([]byte(msg)); err != nil {
        return err
    }
    return nil
}

由於 Write 方法須要一個字節切片([]byte),而須要寫入的是一個字符串,因此要作類型轉換。這種轉換須要進行內存分配和內存複製,但複製後內存又會被立刻拋棄。這裏就會有性能問題,這個內存分配會致使性能降低,須要避開這個內存分配。
在不少包中,實現了 io.Writer 的重要類,都會提供一個對應的高效寫入字符串的 WriteString 方法,好比:內存

  • *http.response : 這個類型不可導出,咱們通常使用的是 http.ResponseWriter 接口
  • *bytes.Buffer
  • *os.File
  • *bufio.Write

因爲 Writer 接口並不包括 WriteString 方法,不能直接調用。這裏能夠先定義一個新的接口,這個接口只包含 WriteString 方法,而後使用類型斷言來判斷 w 的動態類型是否知足這個新接口:字符串

// 將s寫入w,若是w有WriteString方法,就直接調用該方法
func writeString(w io.Writer, s string) (n int, err error) {
    type stringWriter interface {
        WriteString(string) (n int, err error)
    }
    if sw, ok := w.(stringWriter); ok {
        return sw.WriteString(s) // 避免了內存複製
    }
    return w.Write([]byte(s)) // 分配了臨時內存
}

func writeMsg(w io.Writer, msg string) error {
    if _, err := writeString(w, msg); err != nil {
        return err
    }
    return nil
}

因爲上面的操做太常見了,io 包已經提供了一個函數 io.WriteString 能夠直接使用。上面的代碼能夠簡化爲以下的方式:

func writeMsg2(w io.Writer, msg string) error {
    if _, err := io.WriteString(w, msg); err != nil {
        return err
    }
    return nil
}

這裏本質上仍是同樣的,接口的定義、類型檢查都封裝到了io包的內部,而且內部實現的邏輯和上面是同樣的。
以前想要向某個具體的類型寫入字符串的時候,會查看該類型是否有 WriteString 方法。可是若是要操做的類型是 io.Writer 接口,雖然實際背後的動態類型仍是那個類型,可是就沒法調用 WriteString 方法了。這時能夠直接使用 io 包提供的工具函數完成一樣的操做。

經過 fmt.Fprint 寫入

fmt.Fprint 系列的函數,接收的第一個參數是 io.Writer,也能夠進行寫入操做。並且 fmt 包提供的函數,接收空接口,能夠是任何的類型,自帶格式化成字符串的功能,不少時候更方便。 在向 os.Stdout 打印輸出錯誤,以及 Web 服務器向客戶端發送數據的時候使用的比較多,不過一樣的也是能夠向任何的 io.Writer 寫入內容的,好比寫入到文件。

相關文章
相關標籤/搜索