如此乾貨你值得收:Go 語言 bufio 包介紹

更多Go語言知識,歡迎關注微信公衆號:Go語言中文網

bufio 用來幫助處理 I/O 緩存。 我們將通過一些示例來熟悉其爲我們提供的:Reader, Writer and Scanner 等一系列功能

bufio.Writer

多次進行小量的寫操作會影響程序性能。每一次寫操作最終都會體現爲系統層調用,頻繁進行該操作將有可能對 CPU 造成傷害。而且很多硬件設備更適合處理塊對齊的數據,例如硬盤。爲了減少進行多次寫操作所需的開支,golang 提供了 bufio.Writer。數據將不再直接寫入目的地(實現了 io.Writer 接口),而是先寫入緩存,當緩存寫滿後再統一寫入目的地:

producer --> buffer --> io.Writer

下面具體看一下在9次寫入操作中(每次寫入一個字符)具有4個字符空間的緩存是如何工作的:

producer        buffer       destination (io.Writer)
   a    ----->    a
   b    ----->    ab
   c    ----->    abc
   d    ----->    abcd
   e    ----->    e      ----->   abcd
   f    ----->    ef
   g    ----->    efg
   h    ----->    efgh
   i    ----->    i      ----->   abcdefgh

-----> 箭頭代表寫入操作

bufio.Writer 底層使用 []byte 進行緩存

type Writer int
func (*Writer) Write(p []byte) (n int, err error) {
    fmt.Println(len(p))
    return len(p), nil
}
func main() {
    fmt.Println("Unbuffered I/O")
    w := new(Writer)
    w.Write([]byte{'a'})
    w.Write([]byte{'b'})
    w.Write([]byte{'c'})
    w.Write([]byte{'d'})
    fmt.Println("Buffered I/O")
    bw := bufio.NewWriterSize(w, 3)
    bw.Write([]byte{'a'})
    bw.Write([]byte{'b'})
    bw.Write([]byte{'c'})
    bw.Write([]byte{'d'})
    err := bw.Flush()
    if err != nil {
        panic(err)
    }
}
Unbuffered I/O
1
1
1
1
Buffered I/O
3
1

沒有被緩存的 I/O:意味着每一次寫操作都將直接寫入目的地。我們進行4次寫操作,每次寫操作都映射爲對 Write 的調用,調用時傳入的參數爲一個長度爲1的 byte 切片。

使用了緩存的 I/O:我們使用三個字節長度的緩存來存儲數據,當緩存滿時進行一次 flush 操作(將緩存中的數據進行處理)。前三次寫入寫滿了緩存。第四次寫入時檢測到緩存沒有剩餘空間,所以將緩存中的積累的數據寫出。字母 d 被存儲了,但在此之前 Flush 被調用以騰出空間。當緩存被寫到末尾時,緩存中未被處理的數據需要被處理。bufio.Writer 僅在緩存充滿或者顯式調用 Flush 方法時處理(發送)數據。

bufio.Writer 默認使用 4096 長度字節的緩存,可以使用 NewWriterSize 方法來設定該值

實現

實現十分簡單:

type Writer struct {
    err error
    buf []byte
    n   int
    wr  io.Writer
}

字段 buf 用來存儲數據,當緩存滿或者 Flush 被調用時,消費者(wr)可以從緩存中獲取到數據。如果寫入過程中發生了 I/O error,此 error 將會被賦給 err 字段, error 發生之後,writer 將停止操作(writer is no-op):

type Writer int
func (*Writer) Write(p []byte) (n int, err error) {
    fmt.Printf("Write: %q\n", p)
    return 0, errors.New("boom!")
}
func main() {
    w := new(Writer)
    bw := bufio.NewWriterSize(w, 3)
    bw.Write([]byte{'a'})
    bw.Write([]byte{'b'})
    bw.Write([]byte{'c'})
    bw.Write([]byte{'d'})
    err := bw.Flush()
    fmt.Println(err)
}
Write: "abc"
boom!

這裏我們可以看到 Flush 沒有第二次調用消費者的 write 方法。如果發生了 error, 使用了緩存的 writer 不會嘗試再次執行寫操作。

字段 n 標識緩存內部當前操作的位置。Buffered 方法返回 n 的值:

type Writer int
func (*Writer) Write(p []byte) (n int, err error) {
    return len(p), nil
}
func main() {
    w := new(Writer)
    bw := bufio.NewWriterSize(w, 3)
    fmt.Println(bw.Buffered())
    bw.Write([]byte{'a'})
    fmt.Println(bw.Buffered())
    bw.Write([]byte{'b'})
    fmt.Println(bw.Buffered())
    bw.Write([]byte{'c'})
    fmt.Println(bw.Buffered())
    bw.Write([]byte{'d'})
    fmt.Println(bw.Buffered())
}
0
1
2
3
1

n 從 0 開始,當有數據被添加到緩存中時,該數據的長度值將會被加和到 n中(操作位置向後移動)。當bw.Write([] byte{'d'})被調用時,flush會被觸發,n 會被重設爲0。

Large writes

type Writer int
func (*Writer) Write(p []byte) (n int, err error) {
    fmt.Printf("%q\n", p)
    return len(p), nil
}
func main() {
    w := new(Writer)
    bw := bufio.NewWriterSize(w, 3)
    bw.Write([]byte("abcd"))
}

由於使用了 bufio,程序打印了 "abcd"。如果 Writer 檢測到 Write 方法被調用時傳入的數據長度大於緩存的長度(示例中是三個字節)。其將直接調用 writer(目的對象)的 Write 方法。當數據量足夠大時,其會自動跳過內部緩存代理。

重置

緩存是 bufio 的核心部分。通過使用 Reset 方法,Writer 可以用於不同的目的對象。重複使用 Writer 緩存減少了內存的分配。而且減少了額外的垃圾回收工作:

type Writer1 int
func (*Writer1) Write(p []byte) (n int, err error) {
    fmt.Printf("writer#1: %q\n", p)
    return len(p), nil
}
type Writer2 int
func (*Writer2) Write(p []byte) (n int, err error) {
    fmt.Printf("writer#2: %q\n", p)
    return len(p), nil
}
func main() {
    w1 := new(Writer1)
    bw := bufio.NewWriterSize(w1, 2)
    bw.Write([]byte("ab"))
    bw.Write([]byte("cd"))
    w2 := new(Writer2)
    bw.Reset(w2)
    bw.Write([]byte("ef"))
    bw.Flush()
}
writer#1: "ab"
writer#2: "ef"

這段代碼中有一個 bug。在調用 Reset 方法之前,我們應該使用 Flush flush緩存。 由於 Reset 只是簡單的丟棄未被處理的數據,所以已經被寫入的數據 cd 丟失了:

func (b *Writer) Reset(w io.Writer) {
    b.err = nil
    b.n = 0
    b.wr = w
}

緩存剩餘空間

爲了檢測緩存中還剩餘多少空間, 我們可以使用方法 Available

w := new(Writer)
bw := bufio.NewWriterSize(w, 2)
fmt.Println(bw.Available())
bw.Write([]byte{'a'})
fmt.Println(bw.Available())
bw.Write([]byte{'b'})
fmt.Println(bw.Available())
bw.Write([]byte{'c'})
fmt.Println(bw.Available())
2
1
0
1

{Byte,Rune,String}的方法

爲了方便, 我們有三個用來寫普通類型的實用方法:

w := new(Writer)
bw := bufio.NewWriterSize(w, 10)
fmt.Println(bw.Buffered())
bw.WriteByte('a')
fmt.Println(bw.Buffered())
bw.WriteRune('ł') // 'ł' occupies 2 bytes
fmt.Println(bw.Buffered())
bw.WriteString("aa")
fmt.Println(bw.Buffered())
0
1
3
5

ReadFrom

io 包中定義了 io.ReaderFrom 接口。 該接口通常被 writer 實現,用於從指定的 reader 中讀取所有數據(直到 EOF)並對讀到的數據進行底層處理:

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

比如 io.Copy 使用了 io.ReaderFrom 接口

bufio.Writer 實現了此接口:因此我們可以通過調用 ReadFrom 方法來處理從 io.Reader 獲取到的所有數據:

type Writer int
func (*Writer) Write(p []byte) (n int, err error) {
    fmt.Printf("%q\n", p)
    return len(p), nil
}
func main() {
    s := strings.NewReader("onetwothree")
    w := new(Writer)
    bw := bufio.NewWriterSize(w, 3)
    bw.ReadFrom(s)
    err := bw.Flush()
    if err != nil {
        panic(err)
    }
}
"one"
"two"
"thr"
"ee"

使用 ReadFrom 方法的同時,調用 Flush 方法也很重要

bufio.Reader

通過它,我們可以從底層的 io.Reader 中更大批量的讀取數據。這會使讀取操作變少。如果數據讀取時的塊數量是固定合適的,底層媒體設備將會有更好的表現,也因此會提高程序的性能:

io.Reader --> buffer --> consumer

假設消費者想要從硬盤上讀取10個字符(每次讀取一個字符)。在底層實現上,這將會觸發10次讀取操作。如果硬盤按每個數據塊四個字節來讀取數據,那麼 bufio.Reader 將會起到幫助作用。底層引擎將會緩存整個數據塊,然後提供一個可以挨個讀取字節的 API 給消費者:

abcd -----> abcd -----> a
            abcd -----> b
            abcd -----> c
            abcd -----> d
efgh -----> efgh -----> e
            efgh -----> f
            efgh -----> g
            efgh -----> h
ijkl -----> ijkl -----> i
            ijkl -----> j

-----> 代表讀取操作
這個方法僅需要從硬盤讀取三次,而不是10次。

Peek

Peek 方法可以幫助我們查看緩存的前 n 個字節而不會真的『吃掉』它:

  • 如果緩存不滿,而且緩存中緩存的數據少於 n 個字節,其將會嘗試從 io.Reader 中讀取
  • 如果請求的數據量大於緩存的容量,將會返回 bufio.ErrBufferFull
  • 如果 n 大於流的大小,將會返回 EOF

讓我們來看看它是如何工作的:

s1 := strings.NewReader(strings.Repeat("a", 20))
r := bufio.NewReaderSize(s1, 16)
b, err := r.Peek(3)
if err != nil {
    fmt.Println(err)
}
fmt.Printf("%q\n", b)
b, err = r.Peek(17)
if err != nil {
    fmt.Println(err)
}
s2 := strings.NewReader("aaa")
r.Reset(s2)
b, err = r.Peek(10)
if err != nil {
    fmt.Println(err)
}
"aaa"
bufio: buffer full
EOF

bufio.Reader 使用的最小的緩存容器是 16。

返回的切片和被 bufio.Reader 使用的內部緩存底層使用相同的數組。因此引擎底層在執行任何讀取操作之後內部返回的切片將會變成無效的。這是由於其將有可能被其他的緩存數據覆蓋:

s1 := strings.NewReader(strings.Repeat("a", 16) + strings.Repeat("b", 16))
r := bufio.NewReaderSize(s1, 16)
b, _ := r.Peek(3)
fmt.Printf("%q\n", b)
r.Read(make([]byte, 16))
r.Read(make([]byte, 15))
fmt.Printf("%q\n", b)
"aaa"
"bbb"

Reset

就像 bufio.Writer 那樣,緩存也可以用相似的方式被複用。

s1 := strings.NewReader("abcd")
r := bufio.NewReader(s1)
b := make([]byte, 3)
_, err := r.Read(b)
if err != nil {
    panic(err)
}
fmt.Printf("%q\n", b)
s2 := strings.NewReader("efgh")
r.Reset(s2)
_, err = r.Read(b)
if err != nil {
    panic(err)
}
fmt.Printf("%q\n", b)
"abc"
"efg"

通過使用 Reset,我們可以避免冗餘的內存分配和不必要的垃圾回收工作。

Discard

這個方法將會丟棄 n 個字節的,返回時也不會返回被丟棄的 n 個字節。如果 bufio.Reader 緩存了超過或者等於 n 個字節的數據。那麼其將不必從 io.Reader 中讀取任何數據。其只是簡單的從緩存中略去前 n 個字節:

type R struct{}
func (r *R) Read(p []byte) (n int, err error) {
    fmt.Println("Read")
    copy(p, "abcdefghijklmnop")
    return 16, nil
}
func main() {
    r := new(R)
    br := bufio.NewReaderSize(r, 16)
    buf := make([]byte, 4)
    br.Read(buf)
    fmt.Printf("%q\n", buf)
    br.Discard(4)
    br.Read(buf)
    fmt.Printf("%q\n", buf)
}
Read
"abcd"
"ijkl"

調用 Discard 方法將不會從 reader r 中讀取數據。另一種情況,緩存中數據量小於 n,那麼 bufio.Reader 將會讀取需要數量的數據來確保被丟棄的數據量不會少於 n

type R struct{}
func (r *R) Read(p []byte) (n int, err error) {
    fmt.Println("Read")
    copy(p, "abcdefghijklmnop")
    return 16, nil
}
func main() {
    r := new(R)
    br := bufio.NewReaderSize(r, 16)
    buf := make([]byte, 4)
    br.Read(buf)
    fmt.Printf("%q\n", buf)
    br.Discard(13)
    fmt.Println("Discard")
    br.Read(buf)
    fmt.Printf("%q\n", buf)
}
Read
"abcd"
Read
Discard
"bcde"

由於調用了 Discard 方法,所以讀取方法被調用了兩次。

Read

Read 方法是 bufio.Reader 的核心。它和 io.Reader 的唯一方法具有相同的簽名。因此 bufio.Reader 實現了這個普遍存在的接口:

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

bufio.ReaderRead 方法從底層的 io.Reader 中一次讀取最大的數量:

  1. 如果內部緩存具有至少一個字節的數據,那麼無論傳入的切片的大小(len(p))是多少,Read 方法都將僅僅從內部緩存中獲取數據,不會從底層的 reader 中讀取任何數據:

    func (r *R) Read(p []byte) (n int, err error) {
     fmt.Println("Read")
     copy(p, "abcd")
     return 4, nil
    }
    func main() {
     r := new(R)
     br := bufio.NewReader(r)
     buf := make([]byte, 2)
     n, err := br.Read(buf)
     if err != nil {
         panic(err)
     }
     buf = make([]byte, 4)
     n, err = br.Read(buf)
     if err != nil {
         panic(err)
     }
     fmt.Printf("read = %q, n = %d\n", buf[:n], n)
    }
    Read
    read = "cd", n = 2
    

    我們的 io.Reader 實例無線返回「abcd」(不會返回 io.EOF)。 第二次調用 Read並傳入長度爲4的切片,但是內部緩存在第一次從 io.Reader 中讀取數據之後已經具有數據「cd」,所以 bufio.Reader 返回緩存中的數據數據,而不和底層 reader 進行通信。

  2. 如果內部緩存是空的,那麼將會執行一次從底層 io.Reader 的讀取操作。 從前面的例子中我們可以清晰的看到如果我們開啓了一個空的緩存,然後調用:

    n, err := br.Read(buf)
    

    將會觸發讀取操作來填充緩存。

  3. 如果內部緩存是空的,但是傳入的切片長度大於緩存長度,那麼 bufio.Reader 將會跳過緩存,直接讀取傳入切片長度的數據到切片中:

    type R struct{}
    func (r *R) Read(p []byte) (n int, err error) {
     fmt.Println("Read")
     copy(p, strings.Repeat("a", len(p)))
     return len(p), nil
    }
    func main() {
     r := new(R)
     br := bufio.NewReaderSize(r, 16)
     buf := make([]byte, 17)
     n, err := br.Read(buf)
     if err != nil {
         panic(err)
     }
     fmt.Printf("read = %q, n = %d\n", buf[:n], n)
     fmt.Printf("buffered = %d\n", br.Buffered())
    }
    Read
    read = "aaaaaaaaaaaaaaaaa", n = 17
    buffered = 0
    

    bufio.Reader 讀取之後,內部緩存中沒有任何數據(buffered = 0)

{Read, Unread}Byte

這些方法都實現了從緩存中讀取單個字節或者將最後一個讀取的字節返回到緩存:

r := strings.NewReader("abcd")
br := bufio.NewReader(r)
byte, err := br.ReadByte()
if err != nil {
    panic(err)
}
fmt.Printf("%q\n", byte)
fmt.Printf("buffered = %d\n", br.Buffered())
err = br.UnreadByte()
if err != nil {
    panic(err)
}
fmt.Printf("buffered = %d\n", br.Buffered())
byte, err = br.ReadByte()
if err != nil {
    panic(err)
}
fmt.Printf("%q\n", byte)
fmt.Printf("buffered = %d\n", br.Buffered())
'a'
buffered = 3
buffered = 4
'a'
buffered = 3

{Read, Unread}Rune

這兩個方法的功能和前面方法的功能差不多, 但是用來處理 Unicode 字符(UTF-8 encoded)。

ReadSlice

函數返回在第一次出現傳入字節前的字節:

func (b *Reader) ReadSlice(delim byte) (line []byte, err error)

示例:

s := strings.NewReader("abcdef|ghij")
r := bufio.NewReader(s)
token, err := r.ReadSlice('|')
if err != nil {
    panic(err)
}
fmt.Printf("Token: %q\n", token)
Token: "abcdef|"

重要:返回的切面指向內部緩衝區, 因此它可能在下一次讀取操作期間被覆蓋

如果找不到分隔符,而且已經讀到末尾(EOF),將會返回 io.EOF error。 讓我們將上面程序中的一行修改爲如下代碼:

s := strings.NewReader("abcdefghij")

如果數據以 panic: EOF 結尾。 當分隔符找不到而且沒有更多的數據可以放入緩衝區時函數將返回 io.ErrBufferFull:

s := strings.NewReader(strings.Repeat("a", 16) + "|")
r := bufio.NewReaderSize(s, 16)
token, err := r.ReadSlice('|')
if err != nil {
    panic(err)
}
fmt.Printf("Token: %q\n", token)

這一小段代碼會出現錯誤:panic: bufio: buffer full

ReadBytes

func (b *Reader) ReadBytes(delim byte) ([]byte, error)

返回出現第一次分隔符前的所有數據組成的字節切片。 它和 ReadSlice 具有相同的簽名,但是 ReadSlice 是一個低級別的函數,ReadBytes 的實現使用了 ReadSlice。 那麼兩者之間有什麼不同呢? 在分隔符找不到的情況下,ReadBytes 可以多次調用 ReadSlice,而且可以累積返回的數據。 這意味着 ReadBytes 將不再受到 緩存大小的限制:

s := strings.NewReader(strings.Repeat("a", 40) + "|")
r := bufio.NewReaderSize(s, 16)
token, err := r.ReadBytes('|')
if err != nil {
    panic(err)
}
fmt.Printf("Token: %q\n", token)
Token: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|"

另外該函數返回一個新的字節切片,所以沒有數據會被將來的讀取操作覆蓋的風險。

ReadString

它是我們上面討論的 ReadBytes 的簡單封裝:

func (b *Reader) ReadString(delim byte) (string, error) {
    bytes, err := b.ReadBytes(delim)
    return string(bytes), err
}

ReadLine

ReadLine() (line []byte, isPrefix bool, err error)

內部使用 ReadSlice (ReadSlice('\n'))實現,同時從返回的切片中移除掉換行符(\n 或者 \r\n)。 此方法的簽名不同於 ReadBytes 或者 ReadSlice,因爲它包含 isPrefix 標誌。 由於內部緩存無法存儲更多的數據,當找不到分隔符時該標誌爲 true:

s := strings.NewReader(strings.Repeat("a", 20) + "\n" + "b")
r := bufio.NewReaderSize(s, 16)
token, isPrefix, err := r.ReadLine()
if err != nil {
    panic(err)
}
fmt.Printf("Token: %q, prefix: %t\n", token, isPrefix)
token, isPrefix, err = r.ReadLine()
if err != nil {
    panic(err)
}
fmt.Printf("Token: %q, prefix: %t\n", token, isPrefix)
token, isPrefix, err = r.ReadLine()
if err != nil {
    panic(err)
}
fmt.Printf("Token: %q, prefix: %t\n", token, isPrefix)
token, isPrefix, err = r.ReadLine()
if err != nil {
    panic(err)
}
Token: "aaaaaaaaaaaaaaaa", prefix: true
Token: "aaaa", prefix: false
Token: "b", prefix: false
panic: EOF

如果最後一次返回的切片以換行符結尾,此方法將不會給出任何信息:

s := strings.NewReader("abc")
r := bufio.NewReaderSize(s, 16)
token, isPrefix, err := r.ReadLine()
if err != nil {
    panic(err)
}
fmt.Printf("Token: %q, prefix: %t\n", token, isPrefix)
s = strings.NewReader("abc\n")
r.Reset(s)
token, isPrefix, err = r.ReadLine()
if err != nil {
    panic(err)
}
fmt.Printf("Token: %q, prefix: %t\n", token, isPrefix)
Token: "abc", prefix: false
Token: "abc", prefix: false

WriteTo

bufio.Reader 實現了 io.WriterTo 接口:

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

此方法允許我們傳入一個實現了 io.Writer 的消費者。 從生產者讀取的所有數據都將會被送到消費者。 下面通過練習來看看它是如何工作的:

type R struct {
    n int
}
func (r *R) Read(p []byte) (n int, err error) {
    fmt.Printf("Read #%d\n", r.n)
    if r.n >= 10 {
         return 0, io.EOF
    }
    copy(p, "abcd")
    r.n += 1
    return 4, nil
}
func main() {
    r := bufio.NewReaderSize(new(R), 16)
    n, err := r.WriteTo(ioutil.Discard)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Written bytes: %d\n", n)
}
Read #0
Read #1
Read #2
Read #3
Read #4
Read #5
Read #6
Read #7
Read #8
Read #9
Read #10
Written bytes: 40

bufio.Scanner

go語言中對bufio.Scanner的深層分析

ReadBytes(’\n’), ReadString(’\n’), ReadLine 還是 Scanner?

就像前面說的那樣,ReadString('\n') 只是對於 ReadBytes(\n) 的簡單封裝。 所以讓我們來討論一下另外三者之間的不同之處吧。

  1. ReadBytes 不會自動處理 \r\n 序列:

    s := strings.NewReader("a\r\nb")
    r := bufio.NewReader(s)
    for {
     token, _, err := r.ReadLine()
     if len(token) > 0 {
         fmt.Printf("Token (ReadLine): %q\n", token)
     }
     if err != nil {
         break
     }
    }
    s.Seek(0, io.SeekStart)
    r.Reset(s)
    for {
     token, err := r.ReadBytes('\n')
     fmt.Printf("Token (ReadBytes): %q\n", token)
     if err != nil {
         break
     }
    }
    s.Seek(0, io.SeekStart)
    scanner := bufio.NewScanner(s)
    for scanner.Scan() {
     fmt.Printf("Token (Scanner): %q\n", scanner.Text())
    }
    Token (ReadLine): "a"
    Token (ReadLine): "b"
    Token (ReadBytes): "a\r\n"
    Token (ReadBytes): "b"
    Token (Scanner): "a"
    Token (Scanner): "b"
    

    ReadBytes 會將分隔符一起返回,所以需要額外的一些工作來重新處理數據(除非返回分隔符是有用的)。

  2. ReadLine 不會處理超出內部緩存的行:

    s := strings.NewReader(strings.Repeat("a", 20) + "\n")
    r := bufio.NewReaderSize(s, 16)
    token, _, _ := r.ReadLine()
    fmt.Printf("Token (ReadLine): \t%q\n", token)
    s.Seek(0, io.SeekStart)
    r.Reset(s)
    token, _ = r.ReadBytes('\n')
    fmt.Printf("Token (ReadBytes): \t%q\n", token)
    s.Seek(0, io.SeekStart)
    scanner := bufio.NewScanner(s)
    scanner.Scan()
    fmt.Printf("Token (Scanner): \t%q\n", scanner.Text())
    Token (ReadLine):     "aaaaaaaaaaaaaaaa"
    Token (ReadBytes):     "aaaaaaaaaaaaaaaaaaaa\n"
    Token (Scanner):     "aaaaaaaaaaaaaaaaaaaa"
    

    爲了取回流中剩餘的數據,ReadLine 需要被調用兩次。 被 Scanner 處理的最大 token 長度爲 641024。 如果傳入更長的 token,scanner 將無法工作。 當 ReadLine 被多次調用時可以處理任何長度的 token。 由於函數返回是否在緩存數據中找到分隔符的標誌,但是這需要調用者進行處理。 ReadBytes* 則沒有任何限制:

    s := strings.NewReader(strings.Repeat("a", 64*1024) + "\n")
    r := bufio.NewReader(s)
    token, _, err := r.ReadLine()
    fmt.Printf("Token (ReadLine): %d\n", len(token))
    fmt.Printf("Error (ReadLine): %v\n", err)
    s.Seek(0, io.SeekStart)
    r.Reset(s)
    token, err = r.ReadBytes('\n')
    fmt.Printf("Token (ReadBytes): %d\n", len(token))
    fmt.Printf("Error (ReadBytes): %v\n", err)
    s.Seek(0, io.SeekStart)
    scanner := bufio.NewScanner(s)
    scanner.Scan()
    fmt.Printf("Token (Scanner): %d\n", len(scanner.Text()))
    fmt.Printf("Error (Scanner): %v\n", scanner.Err())
    Token (ReadLine): 4096
    Error (ReadLine): <nil>
    Token (ReadBytes): 65537
    Error (ReadBytes): <nil>
    Token (Scanner): 0
    Error (Scanner): bufio.Scanner: token too long
    
  3. 就像上面那樣,Scanner 具有非常簡單的 API,對於普通的例子,它還提供了友好的抽象概念。

bufio.ReadWriter

Go 的結構體中可以使用一種叫做內嵌的類型。 和常規的具有類型和名字的字段不同,我們可以僅僅使用類型(匿名字段)。 內嵌類型的方法或者字段如果不和其他的衝突的話,則可以使用一個簡短的選擇器來引用:

type T1 struct {
    t1 string
}
func (t *T1) f1() {
    fmt.Println("T1.f1")
}
type T2 struct {
    t2 string
}
func (t *T2) f2() {
    fmt.Println("T1.f2")
}
type U struct {
    *T1
    *T2
}
func main() {
    u := U{T1: &T1{"foo"}, T2: &T2{"bar"}}
    u.f1()
    u.f2()
    fmt.Println(u.t1)
    fmt.Println(u.t2)
}
T1.f1
T1.f2
foo
bar

我們可以簡單的使用 u.t1 來代替 u.T1.t1。 包 bufio 使用內嵌的方式來定義 ReadWriter。 它由 ReaderWriter 構成:

type ReadWriter struct {
      *Reader
      *Writer
  }

讓我們來看看它是如何使用的:

s := strings.NewReader("abcd")
br := bufio.NewReader(s)
w := new(bytes.Buffer)
bw := bufio.NewWriter(w)
rw := bufio.NewReadWriter(br, bw)
buf := make([]byte, 2)
_, err := rw.Read(buf)
if err != nil {
    panic(err)
}
fmt.Printf("%q\n", buf)
buf = []byte("efgh")
_, err = rw.Write(buf)
if err != nil {
    panic(err)
}
err = rw.Flush()
if err != nil {
   panic(err)
}
fmt.Println(w.String())
"ab"
efgh

由於 reader 和 writer 都具有方法 Buffered,所以若想獲取緩存數據的量,rw.Buffered() 將無法工作,編譯器會報錯:ambiguous selector rw.Buffered。 但是類似 rw.Reader.Buffered() 的方式是可以的。

bufio + standard library

bufio 包被廣泛使用在 I/O出現的標準庫中,例如:

  • archive/zip
  • compress/*
  • encoding/*
  • image/*
  • 類似於 net/http 的TCP連接包裝。 它還結合一些類似於 sync.Pool 的緩存框架來減少垃圾回收的壓力

via: https://medium.com/golangspec/introduction-to-bufio-package-in-golang-ad7d1877f762

作者:Michał Łowicki 譯者:jliu666 校對:rxcai

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

更多Go語言知識,歡迎關注微信公衆號:Go語言中文網

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