• Go 语言标准库之 bufio 包


    bufio 包实现了缓存I/O。它提供了bufio.Readerbufio.Writer类型,其内部分别包装了io.Readerio.Writer对象,同时分别实现了io.Readerio.Writer接口。同时,该包为文本I/O提供了一些便利操作。

    bufio.Reader 类型

    bufio 包提供了带缓存功能的 Reader,在从硬盘中读取字节前使用内存缓存,可以节省操作硬盘I/O的时间。在一些情况下它也很有用,比如每次读一个字节,可以一次性的从硬盘中读取缓存大小的数据到内存缓存中,然后读取缓存中的字节,减少硬盘的磨损以及提升性能。

    // bufio.Reader 结构包装了一个 io.Reader 对象,提供缓存功能,同时实现了 io.Reader 接口
    type Reader struct {
        buf []byte    // 缓存
        rd  io.Reader // 底层的 io.Reader
        // r:从 buf 中读取的字节(偏移);w:buf 中写入内容的偏移;
        // w - r 是 buf 中可被读的长度(缓存数据的大小),也是 Buffered() 方法的返回值
        r, w         int
        err          error // 读过程中遇到的错误
        lastByte     int   // 最后一次读到的字节值,-1 是无效值
        lastRuneSize int   // 最后一次读到的 Rune 的大小,只有 ReadRune 操作才会修改该值,-1 是无效值
    }
    

    实例化

    bufio 包提供了两个实例化 Reader 对象的函数。其中,NewReader()函数本质上是调用NewReaderSize()函数。具体如下:

    // 创建一个具有默认大小缓存、从 r 读取的 *Reader,默认缓存大小为 4096 字节
    func NewReader(r io.Reader) *Reader
    
    // 创建一个具有最少有 size 大小的缓存、从 r 读取的 *Reader
    // 如果参数 r 已经是一个具有足够大缓存的 *Reader 类型值,会返回 r
    func NewReaderSize(r io.Reader, size int) *Reader
    

    查看NewReader()NewReaderSize()函数源码:

    const (
        defaultBufSize = 4096
    )
    
    func NewReader(rd io.Reader) *Reader {
        return NewReaderSize(rd, defaultBufSize)
    }
    
    func NewReaderSize(rd io.Reader, size int) *Reader {
        // 已经是 bufio.Reader 类型,且缓存大小不小于 size,则直接返回
        b, ok := rd.(*Reader)
        if ok && len(b.buf) >= size {
            return b
        }
        // 缓存大小不会小于 minReadBufferSize (16字节)
        if size < minReadBufferSize {
            size = minReadBufferSize
        }
        r := new(Reader)
        r.reset(make([]byte, size), rd)
        return r
    }
    

    读操作方法

    // 从底层输入流读取最多 len(p) 个字节数据写入到 p 中,返回读取的字节数 n 和遇到的任何错误 err
    // 读取到输入流结尾,n 可能返回非 0,err 返回 nil 或者 io.EOF,但是下次调用肯定返回 (0, io.EOF)
    func (b *Reader) Read(p []byte) (n int, err error)
    
    // 读取并返回一个字节。如果没有可用的数据,会返回错误
    func (b *Reader) ReadByte() (c byte, err error)
    
    // 还原最近一次读取操作读出的最后一个字节,相当于让读偏移量 r 前移一个字节
    // 连续两次 UnreadByte 操作,而中间没有任何读取操作,会返回错误
    func (b *Reader) UnreadByte() error
    
    // 读取一个 utf-8 编码的 unicode 码值,返回该码值、其编码长度和可能的错误
    // 如果 utf-8 编码非法,读取位置只移动 1 字节,返回 U+FFFD,返回值 size 为 1 而 err 为 nil。如果没有可用的数据,会返回错误
    func (b *Reader) ReadRune() (r rune, size int, err error)
    
    // 还原前一次 ReadRune 操作读取的 unicode 码值,相当于让读偏移量 r 前移一个码值长度
    // 需要注意 UnreadRune 方法调用前必须调用 ReadRune 方法(从这点看,UnreadRune 比 UnreadByte 严格很多)
    func (b *Reader) UnreadRune() error
    
    // 读取直到第一次遇到 delim 字节,返回一个包含已读取的数据和 delim 字节的切片
    // 如果 ReadBytes 方法在读取到 delim 之前遇到了错误,它会返回在错误之前读取的数据以及该错误(一般是 io.EOF)
    // 当且仅当 ReadBytes 方法返回的切片不以 delim 结尾时,会返回一个非 nil 的错误
    func (b *Reader) ReadBytes(delim byte) (line []byte, err error)
    
    // 读取直到第一次遇到 delim 字节,返回一个包含已读取的数据和 delim 字节的字符串
    // 如果 ReadString 方法在读取到 delim 之前遇到了错误,它会返回在错误之前读取的数据以及该错误(一般是 io.EOF)
    // 当且仅当 ReadString 方法返回的切片不以 delim 结尾时,会返回一个非 nil 的错误
    func (b *Reader) ReadString(delim byte) (line string, err error)
    

    ☕️ 示例代码

    package main
    
    import (
        "bufio"
        "fmt"
        "io"
        "os"
    )
    
    func main() {
        // 打开文件,只读
        file, err := os.Open("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
    
        // 默认缓存区大小为 4096 字节
        reader := bufio.NewReader(file)
    
        // 读取最多 10 个字节数据到字节切片 b 中
        b := make([]byte, 10)
        n, err := reader.Read(b)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Read %d bytes: %s\n", n, b)
    
        // 读取一个字节, 如果读取不成功会返回 Error
        c, err := reader.ReadByte()
        if err != nil {
            panic(err)
        }
        fmt.Printf("Read 1 byte: %c\n", c)
    
        // 还原最近一次读取操作读出的最后一个字节
        err = reader.UnreadByte()
        if err != nil {
            panic(err)
        }
    
        // 读取一个字符, 如果读取不成功会返回 Error
        r, size, err := reader.ReadRune()
        if err != nil {
            panic(err)
        }
        fmt.Printf("Read 1 character, %d bytes:%c\n", size, r)
    
        // 还原前一次 ReadRune 操作读取的字符
        err = reader.UnreadRune()
        if err != nil {
            panic(err)
        }
    
        // 读取到分隔符,包含分隔符,返回字节切片
        b, err = reader.ReadBytes('\n')
        if err != nil {
            panic(err)
        }
        fmt.Printf("Read bytes: %s", b)
    
        // 读取到分隔符,包含分隔符,返回字符串
        str, err := reader.ReadString('\n')
        if err != nil && err != io.EOF {
            panic(err)
        }
        fmt.Printf("Read string: %s", str)
    }
    
    // test.txt 内容
    // hello world!!!!
    // 你好,世界!!!!
    
    // 控制台输出:
    // Read 10 bytes: hello worl
    // Read 1 byte: d
    // Read 1 character,1 bytes:d
    // Read bytes: d!!!!
    // Read string: 你好,世界!!!!
    

    Peek 方法

    // 返回输入流的下 n 个字节,而不会移动读偏移量 r。返回的 []byte 是 buf 的引用,所以只在下一次调用读取操作前合法
    // 如果 Peek() 返回的切片长度比 n 小,它也会返会一个错误说明原因。如果 n 比缓存尺寸还大,返回的错误将是 ErrBufferFull
    func (b *Reader) Peek(n int) ([]byte, error)
    

    ⭐️ 示例代码

    package main
    
    import (
        "bufio"
        "fmt"
        "os"
    )
    
    func main() {
        // 打开文件,只读
        file, err := os.Open("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
    
        // 默认缓存区大小为 4096 字节
        reader := bufio.NewReader(file)
    
        // 读取下 5 个字节的数据,文件读偏移量不动,返回的字节切片是内部 buf 的引用
        b, err := reader.Peek(5)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Peeked at 5 bytes: %s\n", b)
    
        // 读取下 5 个字节的数据写入字节切片 b 中,文件读偏移量同时移动
        b = make([]byte, 5)
        n, err := reader.Read(b)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Read %d bytes: %s\n", n, b)
    }
    
    // test.txt 内容:
    // hello world!!!!
    // 你好,世界!!!!
    
    // 控制台输出:
    // Peeked at 5 bytes: hello
    // Read 5 bytes: hello
    

    其它方法

    // 返回缓存中现有的可读取的字节数
    func (b *Reader) Buffered() int
    
    // 实现 io.WriteTo 接口。从底层输入流中读取数据,写入输出流 w 中,返回写入的字节数和遇到的任何错误
    func (b *Reader) WriteTo(w io.Writer) (n int64, err error)
    
    // 丢弃缓存中的数据,清除任何错误,将 b 重设为其下层从 r 读取数据
    func (b *Reader) Reset(r io.Reader)
    

    ✏️ 示例代码

    package main
    
    import (
        "bufio"
        "fmt"
        "os"
    )
    
    func main() {
        // 打开文件,只读
        file, err := os.Open("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
    
        // 默认缓存区大小为 4096 字节
        reader := bufio.NewReader(file)
    
        // 读取最多 15 个字节数据到字节切片 b 中
        b := make([]byte, 15)
        n, err := reader.Read(b)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Read %d bytes: %s\n", n, b)
    
        // 返回缓存中现有的可读取的字节数
        fmt.Printf("Unread %d bytes\n", reader.Buffered())
    
        // 将缓存区还未读的数据写入到标准输出
        num, err := reader.WriteTo(os.Stdout)
        if err != nil {
            panic(err)
        }
        fmt.Printf("\nWrite %d bytes\n", num)
    
        // 丢弃缓存中的数据,清除任何错误,重置输入流
        reader.Reset(os.Stdin)
    }
    
    // test.txt 内容:
    // hello world!!!!
    // 你好,世界!!!!
    
    // 控制台输出:
    // Read 15 bytes: hello world!!!!
    // Unread 29 bytes
    //
    // 你好,世界!!!!
    // Write 29 bytes
    

    bufio.Writer 类型

    bufio 包提供了带缓存功能的 Writer,在写字节到硬盘前使用内存缓存,可以节省操作硬盘I/O的时间。在一些情况下它也很有用,比如每次写一个字节,可以把它们攒在内存缓存中,然后一次写入到硬盘中,减少硬盘的磨损以及提升性能。

    // bufio.Writer 结构包装了一个 io.Writer 对象,提供缓存功能,同时实现了 io.Writer 接口
    type Writer struct {
        err error     // 写过程中遇到的错误
        buf []byte    // 缓存
        n   int       // 当前缓存中的字节数
        wr  io.Writer // 底层的 io.Writer 对象
    }
    

    实例化

    bufio 包同样提供了两个实例化 Writer 对象的函数。其中,NewWriter()函数本质上是调用NewWriterSize()函数。具体如下:

    // 创建一个具有默认大小缓存、写入 w 的 *Writer,默认缓存大小为 4096 字节
    func NewWriter(w io.Writer) *Writer
    
    // 创建一个具有最少有 size 尺寸的缓存、写入 w 的 *Writer
    // 如果参数 w 已经是一个具有足够大缓存的 *Writer 类型值,会返回 w
    func NewWriterSize(w io.Writer, size int) *Writer
    

    查看NewWriter()NewWriterSize()函数源码:

    const (
        defaultBufSize = 4096
    )
    
    func NewWriter(w io.Writer) *Writer {
        return NewWriterSize(w, defaultBufSize)
    }
    
    func NewWriterSize(w io.Writer, size int) *Writer {
        // 已经是 bufio.Writer 类型,且缓存大小不小于 size,则直接返回
        b, ok := w.(*Writer)
        if ok && len(b.buf) >= size {
            return b
        }
        if size <= 0 {
            size = defaultBufSize
        }
        return &Writer{
            buf: make([]byte, size),
            wr:  w,
        }
    }
    

    写操作方法

    // 将 p 的内容写入缓存,返回写入的字节数。如果返回值 n < len(p),返回一个错误说明原因
    func (b *Writer) Write(p []byte) (n int, err error)
    
    // 将单个字节写入缓存
    func (b *Writer) WriteByte(c byte) error
    
    // 将一个 unicode 码值写入缓存,返回写入的字节数和可能的错误
    func (b *Writer) WriteRune(r rune) (size int, err error)
    
    // 将一个字符串写入缓存,返回写入的字节数。如果返回值 n < len(s),返回一个错误说明原因
    func (b *Writer) WriteString(s string) (n int, err error)
    
    // 缓存满时,写操作会自动调用 Flush 方法。该方法将缓存数据刷新到底层的 io.Writer 对象
    // 在所有的写操作完成之后,应该调用 Flush 方法使得缓存都写入底层的 io.Writer 对象
    func (b *Writer) Flush() error
    

    示例代码

    package main
    
    import (
        "bufio"
        "fmt"
        "os"
    )
    
    func main() {
        file, err := os.Create("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
    
        // 默认缓存区大小为 4096 字节
        writer := bufio.NewWriter(file)
    
        // 写字节切片到缓存
        n, err := writer.Write([]byte{65, 66, 67})
        if err != nil {
            panic(err)
        }
        fmt.Printf("Bytes written: %d\n", n)
    
        // 写单个字节到缓存
        err = writer.WriteByte('!')
        if err != nil {
            panic(err)
        }
        fmt.Printf("Bytes written: 1\n")
    
        // 写单个字符到缓存
        n, err = writer.WriteRune('您')
        if err != nil {
            panic(err)
        }
        fmt.Printf("Bytes written: %d\n", n)
    
        // 写字符串到缓存
        n, err = writer.WriteString("Hello World")
        if err != nil {
            panic(err)
        }
        fmt.Printf("Bytes written: %d\n", n)
    
        // 将缓存数据刷新到底层的 io.Writer 对象
        writer.Flush()
    }
    
    // 文件 test.txt 内容:
    // ABC!您Hello World
    
    // 控制台输出:
    // Bytes written: 3
    // Bytes written: 1
    // Bytes written: 3 
    // Bytes written: 12
    

    其它方法

    // 返回缓存中已使用的字节数
    func (b *Writer) Buffered() int
    
    // 返回缓存中还有多少字节未使用
    func (b *Writer) Available() int
    
    // 实现了 io.ReaderFrom 接口。从输入流 r 中读取数据,写入底层输出流中,返回读取的字节数和遇到的任何错误
    func (b *Writer) ReadFrom(r io.Reader) (n int64, err error)
    
    // 丢弃缓存中的数据,清除任何错误,将 b 重设为将其输出写入 w
    func (b *Writer) Reset(w io.Writer)
    

    ✌ 示例代码

    package main
    
    import (
        "bufio"
        "fmt"
        "os"
        "strings"
    )
    
    func main() {
        file, err := os.Create("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
    
        // 默认缓存区大小为 4096 字节
        writer := bufio.NewWriter(file)
    
        // 写字符串到缓存
        n, err := writer.WriteString("Hello World\n")
        if err != nil {
            panic(err)
        }
        fmt.Printf("Bytes written: %d\n", n)
    
        // 检查缓存中的已使用的字节大小
        unFlushedNum := writer.Buffered()
        fmt.Printf("Bytes buffered: %d\n", unFlushedNum)
    
        // 检查缓存中未使用的字节大小
        availableNum := writer.Available()
        if err != nil {
            panic(err)
        }
        fmt.Printf("Available buffer: %d\n", availableNum)
    
        // 从输入流 r 读取数据,写入文件 file 中
        r := strings.NewReader("您好!!!")
        num, err := writer.ReadFrom(r)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Read %d bytes\n", num)
    
        // 检查缓存中的已使用的字节大小
        unFlushedNum = writer.Buffered()
        fmt.Printf("Bytes buffered: %d\n", unFlushedNum)
    
        // 检查缓存中未使用的字节大小
        availableNum = writer.Available()
        if err != nil {
            panic(err)
        }
        fmt.Printf("Available buffer: %d\n", availableNum)
    
        // 将缓存数据刷新到底层的 io.Writer 对象
        writer.Flush()
    
        // 丢弃缓存中的数据,清除任何错误,将 b 重设为将其输出写入 w
        writer.Reset(writer)
    }
    
    // 文件 test.txt 内容:
    // Hello World
    // 您好!!!
    
    // 控制台输出:
    // Bytes written: 12
    // Bytes buffered: 12    
    // Available buffer: 4084
    // Read 15 bytes         
    // Bytes buffered: 27    
    // Available buffer: 4069
    

    bufio.ReadWriter 类型

    bufio 包提供ReadWriter结构存储了bufio.Readerbufio.Writer类型的指针(内嵌),同时实现了io.ReadWriter接口。

    type ReadWriter struct {
        *Reader
        *Writer
    }
    
    // 创建一个新的、将读写操作分派给 r 和 w 的 ReadWriter
    func NewReadWriter(r *Reader, w *Writer) *ReadWriter
    

    bufio.Scanner 类型

    Scanner 是 bufio 包下的类型,在处理文件中以分隔符分隔的文本时很有用。通常我们使用换行符作为分隔符将文件内容分成多行。在 CSV 文件中,逗号一般作为分隔符。os.File文件可以被包装成bufio.Scanner,它就像一个缓存 Reader。我们会调用Scan()方法去读取下一个分隔符,使用Text()或者Bytes()获取读取的数据。

    分隔符可以不是一个简单的字节或者字符,有一个特殊的方法可以实现分隔符的功能,以及将指针移动多少,返回什么数据。如果没有定制的SplitFunc提供,缺省的ScanLines()会使用 newline 字符作为分隔符,其它的分隔函数还包括ScanRunes()ScanWords(),皆在 bufio 包中。

    // SplitFunc 类型代表用于对输出作词法分析的分割函数
    // 参数 data 是尚未处理的数据的一个开始部分的切片,参数 atEOF 表示是否 Reader 接口不能提供更多的数据。
    // 返回值是解析位置前进的字节数,将要返回给调用者的 token 切片,以及可能遇到的错误。如果数据不足以(保证)
    // 生成一个完整的 token,例如需要一整行数据但 data 里没有换行符,SplitFunc 可以返回(0, nil, nil)来告
    // 诉 Scanner 读取更多的数据写入切片然后用从同一位置起始、长度更长的切片再试一次(调用 SplitFunc 类型函数)。
    // 如果返回值 err 非 nil,扫描将终止并将该错误返回给 Scanner 的调用者。
    // 除非 atEOF 为真,永远不会使用空切片 data 调用 SplitFunc 类型函数。然而,如果 atEOF 为真,data 却
    // 可能是非空的、且包含着未处理的文本。
    type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)
    
    // 下面是 bufio 包定义的用于 Scanner 类型的分割函数(符合 SplitFunc)
    
    // 本函数会将每个字节作为一个 token 返回
    func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)
    
    // 本函数会将每个 utf-8 编码的 unicode 码值作为一个 token 返回。
    // 本函数返回的 rune 序列和 range 一个字符串的输出 rune 序列相同。错误的 utf-8 编码会翻译为 U+FFFD = 
    // "\xef\xbf\xbd",但只会消耗一个字节。调用者无法区分正确编码的 rune 和错误编码的 rune。
    func ScanRunes(data []byte, atEOF bool) (advance int, token []byte, err error)
    
    // 本函数会将空白(参见unicode.IsSpace)分隔的片段(去掉前后空白后)作为一个 token 返回。本函数永远不会返回空字符串
    func ScanWords(data []byte, atEOF bool) (advance int, token []byte, err error)
    
    // 本函数会将每一行文本去掉末尾的换行标记作为一个 token 返回,返回的行可以是空字符串。
    // 换行标记为一个可选的回车后跟一个必选的换行符。最后一行即使没有换行符也会作为一个 token 返回
    func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error)
    
    // bufio.Scanner 类型提供了方便的读取数据的接口,如从换行符分隔的文本里读取每一行
    type Scanner struct {
        r            io.Reader // The reader provided by the client.
        split        SplitFunc // The function to split the tokens.
        maxTokenSize int       // Maximum size of a token; modified by tests.
        token        []byte    // Last token returned by split.
        buf          []byte    // Buffer used as argument to split.
        start        int       // First non-processed byte in buf.
        end          int       // End of data in buf.
        err          error     // Sticky error.
        empties      int       // Count of successive empty tokens.
        scanCalled   bool      // Scan has been called; buffer is in use.
        done         bool      // Scan has finished.
    }
    
    // 创建并返回一个从 r 读取数据的 Scanner,默认的分割函数是 ScanLines
    func NewScanner(r io.Reader) *Scanner
    
    // 设置该 Scanner 的分割函数。本方法必须在 Scan 之前调用
    func (s *Scanner) Split(split SplitFunc)
    
    // 获取当前位置生成的 token(该 token 可以通过 Bytes 或 Text 方法获得),并让 Scanner 的扫描位置移动到下一个 token。
    // 当扫描因为抵达输入流结尾或者遇到错误而停止时,本方法会返回 false。在 Scan 方法返回 false 后,Err 方
    // 法将返回扫描时遇到的任何错误;除非是 io.EOF,此时 Err 会返回 nil
    func (s *Scanner) Scan() bool
    
    // 返回最近一次 Scan 调用生成的 token。底层数组指向的数据可能会被下一次 Scan 的调用重写
    func (s *Scanner) Bytes() []byte
    
    // 返回最近一次 Scan 调用生成的 token,会申请创建一个字符串保存 token 并返回该字符串
    func (s *Scanner) Text() string
    
    // 返回 Scanner 遇到的第一个非 EOF 的错误
    func (s *Scanner) Err() error
    

    ✍ 示例代码

    分别使用bufio.Readerbufio.Scanner对象读取文件中的数据,一次读取一行:

    // 使用 bufio.Reader 对象的 ReadBytes() 或 ReadString() 方法读取文件中的数据,一次读取一行
    package main
    
    import (
        "bufio"
        "fmt"
        "io"
        "os"
    )
    
    func main() {
        file, err := os.Create("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
        file.WriteString("http://studygolang.com.\nIt is the home of gophers.\nIf you are studying golang, welcome you!")
        // 将文件 offset 设置到文件开头
        file.Seek(0, io.SeekStart)
    
        // 使用 Reader.ReadString() 方法读取文件
        reader := bufio.NewReader(file)
        for {
            str, err := reader.ReadString('\n')
            if len(str) != 0 {
                fmt.Printf(str)
            }
    
            if err == io.EOF {
                break
            } else if err != nil {
                panic(err)
            }
        }
    }
    
    // 控制台输出
    // http://studygolang.com.
    // It is the home of gophers.              
    // If you are studying golang, welcome you!
    
    // 使用 bufio.Scanner 对象的 Scan() 和 Text()/Bytes() 方法读取文件中的数据,一次读取一行
    package main
    
    import (
        "bufio"
        "fmt"
        "io"
        "os"
    )
    
    func main() {
        file, err := os.Create("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
        file.WriteString("http://studygolang.com.\nIt is the home of gophers.\nIf you are studying golang, welcome you!")
        // 将文件 offset 设置到文件开头
        file.Seek(0, io.SeekStart)
    
        // 使用 Scanner 对象读取文件
        scanner := bufio.NewScanner(file)
        // Scan(): 获取当前位置生成的 token,移动到下一个 token
        for scanner.Scan() {
            // Text()/Bytes():返回最近一次 Scan 调用生成的 token
            fmt.Println(scanner.Text())
        }
        // Err():返回 Scanner 遇到的第一个非 EOF 的错误
        if err := scanner.Err(); err != nil {
            panic(err)
        }
    }
    
    // 控制台输出:
    // http://studygolang.com.
    // It is the home of gophers.
    // If you are studying golang, welcome you!
    

    示例代码

    使用bufio.Scanner对象统计文本中有多少个单词(不排除重复):

    package main
    
    import (
        "bufio"
        "fmt"
        "io"
        "os"
    )
    
    func main() {
        file, err := os.Create("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
        file.WriteString("hello World gophers")
        // 将文件 offset 设置到文件开头
        file.Seek(0, io.SeekStart)
    
        scanner := bufio.NewScanner(file)
        // 缺省的分隔函数是 bufio.ScanLines,我们这里使用 ScanWords
        // 也可以定制一个 SplitFunc 类型的分隔函数
        scanner.Split(bufio.ScanWords)
    
        count := 0
        // Scan(): 获取当前位置生成的 token,移动到下一个 token
        for scanner.Scan() {
            count++
            // Text()/Bytes():返回最近一次 Scan 调用生成的 token
            fmt.Println(scanner.Text())
        }
        // Err():返回 Scanner 遇到的第一个非 EOF 的错误
        if err := scanner.Err(); err != nil {
            panic(err)
        }
        fmt.Println(count)
    }
    
    // 控制台输出:
    // hello
    // World  
    // gophers
    // 3   
    

    参考

    1. bufio — 缓存 IO
    2. Go语言的30个常用文件操作,总有一个你会用到
  • 相关阅读:
    从输入url到页面加载完成都发生了什么?
    JS 获取和监听屏幕方向变化(portrait / landscape)
    圣杯布局的几种情况
    闭包的使用 — 点击列表项输出项目索引
    论文笔记:dropout
    论文笔记:蒸馏网络(Distilling the Knowledge in Neural Network)
    ng-深度学习-课程笔记-17: 序列模型和注意力机制(Week3)
    ng-深度学习-课程笔记-16: 自然语言处理与词嵌入(Week2)
    ng-深度学习-课程笔记-15: 循环序列模型(Week1)
    Ubuntu安装dlib后import出现libstdc++.so.6: version `GLIBCXX_3.4.21' not found
  • 原文地址:https://www.cnblogs.com/zongmin/p/15712463.html
Copyright © 2020-2023  润新知