bufio 套件


io.Readerio.Writer 定義了基於位元組的讀寫行為,然而許多情況下,你會想要基於字串、行來進行讀寫,這可以透過 bufio 套件的 bufio.Readerbufio.Writer 等達到。

bufio.Reader 可以透過 NewReaderNewReaderSize 指定 io.Reader 來建立實例,前者指定預設緩衝區大小 4096 位元組呼叫後者,bufio.Reader 在讀取來源時會從底層的 io.Reader 將資料讀入,在建立 bufio.Reader 實例之後,可以使用的方法有:

func (b *Reader) Buffered() int
func (b *Reader) Discard(n int) (discarded int, err error)
func (b *Reader) Peek(n int) ([]byte, error)
func (b *Reader) Read(p []byte) (n int, err error)
func (b *Reader) ReadByte() (byte, error)
func (b *Reader) ReadBytes(delim byte) ([]byte, error)
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
func (b *Reader) ReadRune() (r rune, size int, err error)
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
func (b *Reader) ReadString(delim byte) (string, error)
func (b *Reader) Reset(r io.Reader)
func (b *Reader) Size() int
func (b *Reader) UnreadByte() error
func (b *Reader) UnreadRune() error
func (b *Reader) WriteTo(w io.Writer) (n int64, err error)

因此對於逐行讀取一個 UTF-8 文字檔案來說,可以簡單地撰寫如下:

package main

import (
    "bufio"
    "os"
    "fmt"
    "io"
)

func printFile(f *os.File) (err error){
    var (
        r = bufio.NewReader(f)
        line string
    )
    for err == nil {
        line, err = r.ReadString('\n')
        fmt.Println(line)
    }
    if err == io.EOF {
        err = nil
    }
    return
}

func main() {
    var filename string
    fmt.Print("檔案名稱:")
    fmt.Scanf("%s", &filename);

    f, err := os.Open(filename)
    if err != nil {
        panic(err)
    }
    defer f.Close()

    printFile(f)
}

如果實際上是要讀取之後寫到另一個輸出,使用 WriteTo 方法更為方便:

package main

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

func main() {
    var filename string
    fmt.Print("檔案名稱:")
    fmt.Scanf("%s", &filename);

    f, err := os.Open(filename)
    if err != nil {
        panic(err)
    }
    defer f.Close()

    bufio.NewReader(f).WriteTo(os.Stdout)
}

Go 在 io.WriteTo 介面定義了 WriteTo 行為:

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

實際上 bufio.Reader 實作了 io 中一些介面,io.WriteTo 只是其中之一;類似地,如果要建立 bufio.Writer 實例,可以透過 NewWriterNewWriterSize 函式,建立之後可用的方法如下:

func (b *Writer) Available() int
func (b *Writer) Buffered() int
func (b *Writer) Flush() error
func (b *Writer) ReadFrom(r io.Reader) (n int64, err error)
func (b *Writer) Reset(w io.Writer)
func (b *Writer) Size() int
func (b *Writer) Write(p []byte) (nn int, err error)
func (b *Writer) WriteByte(c byte) error
func (b *Writer) WriteRune(r rune) (size int, err error)
func (b *Writer) WriteString(s string) (int, error)

bufio.Writer 實作了 io 中一些介面,像是 io.ReadFrom,因此,也可以如下在標準輸出中,顯示讀入的的檔案內容:

package main

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

func main() {
    var filename string
    fmt.Print("檔案名稱:")
    fmt.Scanf("%s", &filename);

    f, err := os.Open(filename)
    if err != nil {
        panic(err)
    }
    defer f.Close()

    w := bufio.NewWriter(os.Stdout)
    w.ReadFrom(f)
    w.Flush()
}

NewWriter 預設的緩衝區為 4096 位元組,由於這邊使用標準輸出,在緩衝區未滿前,資料不會寫出,可以使用 Flush 來出清緩衝區中的資料。

事實上,對於需要逐行讀取的需求,使用 bufio.Scanner 會比較方便,可以使用 NewScanner 來建立實例,建立之後有以下的方法可以使用:

func (s *Scanner) Buffer(buf []byte, max int)
func (s *Scanner) Bytes() []byte
func (s *Scanner) Err() error
func (s *Scanner) Scan() bool
func (s *Scanner) Split(split SplitFunc)
func (s *Scanner) Text() string

來看看讀取文字檔案的例子:

package main

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

func main() {
    var filename string
    fmt.Print("檔案名稱:")
    fmt.Scanf("%s", &filename);

    f, err := os.Open(filename)
    if err != nil {
        panic(err)
    }
    defer f.Close()

    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    if err := scanner.Err(); err != nil {
        panic(err)
    }
}