当前位置: 首页 > 工具软件 > go-summer > 使用案例 >

Go语言从入门到规范-6.3、Go语言IO操作-io相关包和bufio

萧晔
2023-12-01

Go语言从入门到规范-6.3、Go语言IO操作-io相关包和bufio


1. 前言

Go的IO通过我们之前学习总结的Go常用包可以看出来基本有io、ioutil、bufio三个,bufio是我们在业务层面开发时需要学习了解的重点,本文的重点也是bufio,对于io和ioutil有相关性,所以简单带过,目前不做专门总结,感兴趣的小伙伴可以自行学习。(关于I/O这里就不多说了,简单理解就是数据的输入/输出,一般文件操作、网络编程学习前都需要先了解I/O,因为文件和网络等都牵扯到数据的I/O,我们之前已经在os包中接触过了file操作的基本接口,这里我们再对I/O做简单的了解和总结,方便理解常用的文件操作和I/O接口如何结合使用,灵活进行文件读写等操作,以及为后续学习net/http/rpc等包相关内容做铺垫)

2. io、ioutil、bufio

官网标准包地址:

io 包为I/O原语提供了基础的接口. 它主要包装了这些原语的已有实现,如 os 包中的那些,抽象成函数性的共享公共接口, 加上一些其它相关的原语。

由于这些接口和原语以不同的实现包装了低级操作,因此除非另行通知, 否则客户不应假定它们对于并行执行是安全的。

所以我们直接使用io包的情况很少,只是提供了基础接口便于我们扩展,也就是我们也可以利用io实现ioutil、bufio等类似功能。

ioutil 实现了一些I/O的工具函数。没有特殊业务需要的话,其实ioutil还是蛮有用的,简单粗暴。

bufio 包实现了带缓存的I/O操作. 它封装了一个io.Reader或者io.Writer对象,另外创建了一个对象 (Reader或者Writer),这个对象也实现了一个接口,并提供缓冲和文档读写的帮助。

3. bufio

3.1. 常量

const (
    // MaxScanTokenSize is the maximum size used to buffer a token.
    // The actual maximum token size may be smaller as the buffer
    // may need to include, for instance, a newline.
    MaxScanTokenSize = 64 * 1024
)

3.2. 变量

var (
    ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
    ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
    ErrBufferFull        = errors.New("bufio: buffer full")
    ErrNegativeCount     = errors.New("bufio: negative count")
)
var (
    ErrTooLong         = errors.New("bufio.Scanner: token too long")
    ErrNegativeAdvance = errors.New("bufio.Scanner: SplitFunc returns negative advance count")
    ErrAdvanceTooFar   = errors.New("bufio.Scanner: SplitFunc returns advance count beyond input")
)

Errors returned by Scanner.

3.3. func ScanBytes

func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)

ScanBytes是用于Scanner类型的分割函数(符合SplitFunc), 本函数会将每个字节作为一个token返回。

3.4. func ScanLines

func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error)

ScanLines是用于Scanner类型的分割函数(符合SplitFunc),本函数会将每一行文本去掉末尾的换行标记作为一个token返回。返回的行可以是空字符串。换行标记为一个可选的回车后跟一个必选的换行符。最后一行即使没有换行符也会作为一个token返回。

3.5. func ScanRunes

func ScanRunes(data []byte, atEOF bool) (advance int, token []byte, err error)

ScanRunes是用于Scanner类型的分割函数(符合SplitFunc), 本函数会将每个utf-8编码的unicode码值作为一个token返回。 本函数返回的rune序列和range一个字符串的输出rune序列相同。 错误的utf-8编码会翻译为U+FFFD = “\xef\xbf\xbd”, 但只会消耗一个字节。调用者无法区分正确编码的rune和错误编码的rune。

3.6. func ScanWords

func ScanWords(data []byte, atEOF bool) (advance int, token []byte, err error)

ScanWords是用于Scanner类型的分割函数(符合SplitFunc), 本函数会将每一行文本去掉末尾的换行标记作为一个token返回。 返回的行可以是空字符串。换行标记为一个可选的回车后跟一个必选的换行符。 最后一行即使没有换行符也会作为一个token返回。

3.7. type ReadWriter

type ReadWriter struct {
    *Reader
    *Writer
}

ReadWriter存储输入输出指针。 它实现了io.ReadWriter。

(1). func NewReadWriter

func NewReadWriter(r *Reader, w *Writer) *ReadWriter

NewReadWriter分配新的ReadWriter来进行r和w的调度。

3.8. type Reader

type Reader struct {
    // contains filtered or unexported fields
}

Reader实现了对一个io.Reader对象的缓冲读。

(1). func NewReader

func NewReader(rd io.Reader) *Reader

NewReader返回一个新的Reader,这个Reader的大小是默认的大小。

(2).func NewReaderSize

func NewReaderSize(rd io.Reader, size int) *Reader

NewReaderSize返回了一个新的读取器,这个读取器的缓存大小至少大于制定的大小。 如果io.Reader参数已经是一个有足够大缓存的读取器,它就会返回这个Reader了。

(3). func (*Reader) Buffered

func (b *Reader) Buffered() int

Buffered返回当前缓存的可读字节数。

(4). func (*Reader) Discard

func (b *Reader) Discard(n int) (discarded int, err error)

Discard跳过后n个字节,返回丢弃的字节数。

如果Discard跳过小于n个字节,它也会返回一个错误。如果0 <= n <= b.b buffered(),则Discard保证不会从底层io.Reader中读取。

(5). func (*Reader) Peek

func (b *Reader) Peek(n int) ([]byte, error)

Peek返回没有读取的下n个字节。在下个读取的调用前,字节是不可见的。如果Peek返回的字节数少于n, 它一定会解释为什么读取的字节数段了。如果n比b的缓冲大小更大,返回的错误是ErrBufferFull。

(6). func (*Reader) Read

func (b *Reader) Read(p []byte) (n int, err error)

Read读取数据到p。 返回读取到p的字节数。 底层读取最多只会调用一次Read,因此n会小于len§。 在EOF之后,调用这个函数返回的会是0和io.Eof。

(7). func (*Reader) ReadByte

func (b *Reader) ReadByte() (c byte, err error)

ReadByte读取和回复一个单字节。 如果没有字节可以读取,返回一个error。

(8). func (*Reader) ReadBytes

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

ReadBytes读取输入到第一次终止符发生的时候,返回的slice包含从当前到终止符的内容(包括终止符)。 如果ReadBytes在遇到终止符之前就捕获到一个错误,它就会返回遇到错误之前已经读取的数据,和这个捕获 到的错误(经常是 io.EOF)。当返回的数据没有以终止符结束的时候,ReadBytes返回err != nil。 对于简单的使用,或许 Scanner 更方便。

(9). func (*Reader) ReadLine

func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)

ReadLine是一个底层的原始读取命令。许多调用者或许会使用ReadBytes(’\n’)或者ReadString(’\n’)来代替这个方法。

ReadLine尝试返回单个行,不包括行尾的最后一个分隔符。如果一个行大于缓存,调用的时候返回了ifPrefix, 就会返回行的头部。行剩余的部分就会在下次调用的时候返回。当调用行的剩余的部分的时候,isPrefix将会设为false, 返回的缓存只能在下次调用ReadLine的时候看到。ReadLine会返回了一个非空行,或者返回一个error, 但是不会两者都返回。

ReadLine返回的文本不会包含行结尾("\r\n"或者"\n")。如果输入没有最终的行结尾的时候,不会返回 任何迹象或者错误。在 ReadLine 之后调用 UnreadByte 将总是放回读取的最后一个字节 (可能是属于该行末的字符),即便该字节并非 ReadLine 返回的行的一部分。

(10). func (*Reader) ReadRune

func (b *Reader) ReadRune() (r rune, size int, err error)

ReadRune读取单个的UTF-8编码的Unicode字节,并且返回rune和它的字节大小。 如果编码的rune是可见的,它消耗一个字节并且返回1字节的unicode.ReplacementChar (U+FFFD)。

(11). func (*Reader) ReadSlice

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

ReadSlice从输入中读取,直到遇到第一个终止符为止,返回一个指向缓存中字节的slice。在下次调用的时候这些字节就是已经被读取了。如果ReadSlice在找到终止符之前遇到了error, 它就会返回缓存中所有的数据和错误本身(经常是 io.EOF)。 如果在终止符之前缓存已经被充满了,ReadSlice会返回ErrBufferFull错误。由于ReadSlice返回的数据会被下次的I/O操作重写,因此许多的客户端会选择使用ReadBytes或者ReadString代替。当且仅当数据没有以终止符结束的时候,ReadSlice返回err != nil

(12). func (*Reader) ReadString

func (b *Reader) ReadString(delim byte) (line string, err error)

ReadString读取输入到第一次终止符发生的时候,返回的string包含从当前到终止符的内容(包括终止符)。 如果ReadString在遇到终止符之前就捕获到一个错误,它就会返回遇到错误之前已经读取的数据,和这个捕获 到的错误(经常是 io.EOF)。当返回的数据没有以终止符结束的时候,ReadString返回err != nil。 对于简单的使用,或许 Scanner 更方便。

(13). func (*Reader) Reset

func (b *Reader) Reset(r io.Reader)

Reset丢弃缓冲中的数据,清除任何错误,将b重设为其下层从r读取数据。

(14). func (*Reader) UnreadByte

func (b *Reader) UnreadByte() error

UnreadByte将最后的字节标志为未读。只有最后的字节才可以被标志为未读。

(15). func (*Reader) UnreadRune

func (b *Reader) UnreadRune() error

UnreadRune将最后一个rune设置为未读。如果最新的在buffer上的操作不是ReadRune,则UnreadRune 就返回一个error。(在这个角度上看,这个函数比UnreadByte更严格,UnreadByte会将最后一个读取 的byte设置为未读。)

(16). func (*Reader) WriteTo

func (b *Reader) WriteTo(w io.Writer) (n int64, err error)

WriteTo实现了io.WriterTo。

3.9. type Scanner

type Scanner struct {
    // contains filtered or unexported fields
}

Scanner类型提供了方便的读取数据的接口,如从换行符分隔的文本里读取每一行。

成功调用的Scan方法会逐步提供文件的token, 跳过token之间的字节。token由SplitFunc类型的分割函数指定; 默认的分割函数会将输入分割为多个行,并去掉行尾的换行标志。 本包预定义的分割函数可以将文件分割为行、字节、unicode码值、空白分隔的word。 调用者可以定制自己的分割函数。

扫描会在抵达输入流结尾、遇到的第一个I/O错误、token过大不能保存进缓冲时, 不可恢复的停止。当扫描停止后,当前读取位置可能会远在最后一个获得的token后面。 需要更多对错误管理的控制或token很大,或必须从reader连续扫描的程序, 应使用bufio.Reader代替。

(1). 示例 (Custom)

使用带有自定义拆分函数Scanner(通过对ScanWords进行换行构建)来验证32位十进制输入。

代码:

// An artificial input source.
const input = "1234 5678 1234567901234567890"
scanner := bufio.NewScanner(strings.NewReader(input))
// Create a custom split function by wrapping the existing ScanWords function.
split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
    advance, token, err = bufio.ScanWords(data, atEOF)
    if err == nil && token != nil {
        _, err = strconv.ParseInt(string(token), 10, 32)
    }
    return
}
// Set the split function for the scanning operation.
scanner.Split(split)
// Validate the input
for scanner.Scan() {
    fmt.Printf("%s\n", scanner.Text())
}

if err := scanner.Err(); err != nil {
    fmt.Printf("Invalid input: %s", err)
}

输出:

1234
5678
Invalid input: strconv.ParseInt: parsing "1234567901234567890": value out of range

(2). 示例 (Lines)

Scanner的最简单用途,将标准输入作为一组行读取。

package main

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

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        fmt.Println(scanner.Text()) // Println will add back the final '\n'
    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading standard input:", err)
    }
}

(3). 示例 (Words)

通过扫描以空格分隔的符号序列的形式输入,使用Scanner程序来实现一个简单的单词计数实用程序。

package main

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

func main() {
    // An artificial input source.
    const input = "Now is the winter of our discontent,\nMade glorious summer by this sun of York.\n"
    scanner := bufio.NewScanner(strings.NewReader(input))
    // Set the split function for the scanning operation.
    scanner.Split(bufio.ScanWords)
    // Count the words.
    count := 0
    for scanner.Scan() {
        count++
    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading input:", err)
    }
    fmt.Printf("%d\n", count)
}

(4). func NewScanner

func NewScanner(r io.Reader) *Scanner

NewScanner创建并返回一个从r读取数据的Scanner,默认的分割函数是ScanLines。

(5). func (*Scanner) Bytes

func (s *Scanner) Bytes() []byte

Bytes方法返回最近一次Scan调用生成的token。 底层数组指向的数据可能会被下一次Scan的调用重写。

(6). func (*Scanner) Err

func (s *Scanner) Err() error

Err返回Scanner遇到的第一个非EOF的错误。

(7). func (*Scanner) Scan

func (s *Scanner) Scan() bool

Scan方法获取当前位置的token(该token可以通过Bytes或Text方法获得), 并让Scanner的扫描位置移动到下一个token。 当扫描因为抵达输入流结尾或者遇到错误而停止时, 本方法会返回false。在Scan方法返回false后, Err方法将返回扫描时遇到的任何错误;除非是io.EOF,此时Err会返回nil。 若 split 函数返回了 100 个空标记而没有推进输入,那么它就会派错(panic)。这是 scanner 的一个常见错误。

(8). func (*Scanner) Split

func (s *Scanner) Split(split SplitFunc)

Split设置该Scanner的分割函数。本方法必须在Scan之前调用。

(9). func (*Scanner) Text

func (s *Scanner) Text() string

Bytes方法返回最近一次Scan调用生成的token, 会申请创建一个字符串保存token并返回该字符串。

3.10. type SplitFunc

type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)

SplitFunc类型代表用于对输出作词法分析的分割函数。

参数data是尚未处理的数据的一个开始部分的切片, 参数atEOF表示是否Reader接口不能提供更多的数据。 返回值是解析位置前进的字节数,将要返回给调用者的token切片, 以及可能遇到的错误。如果数据不足以(保证)生成一个完整的token, 例如需要一整行数据但data里没有换行符, SplitFunc可以返回(0, nil, nil)来告诉Scanner读取更多的数据 写入切片然后用从同一位置起始、长度更长的切片再试一次(调用SplitFunc类型函数)。

如果返回值err非nil,扫描将终止并将该错误返回给Scanner的调用者。

除非atEOF为真,永远不会使用空切片data调用SplitFunc类型函数。 然而,如果atEOF为真,data却可能是非空的、且包含着未处理的文本。

3.11. type Writer

type Writer struct {
    // contains filtered or unexported fields
}

Writer实现了io.Writer对象的缓存。 如果在写数据到Writer的时候出现了一个错误,不会再有数据被写进来了, 并且所有随后的写操作都会返回error。当所有数据被写入后,客户端应调用 Flush 方法以确保所有数据已转为基本的 io.Writer

示例

package main

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

func main() {
    w := bufio.NewWriter(os.Stdout)
    fmt.Fprint(w, "Hello, ")
    fmt.Fprint(w, "world!")
    w.Flush() // Don't forget to flush!
}

(1). func NewWriter

func NewWriter(w io.Writer) *Writer

NewWriter返回一个新的,有默认尺寸缓存的Writer。

(2). func NewWriterSize

func NewWriterSize(w io.Writer, size int) *Writer

NewWriterSize返回一个新的Writer,它的缓存一定大于指定的size参数。 如果io.Writer参数已经是足够大的有缓存的Writer了,函数就会返回它底层的Writer。

(3). func (*Writer) Available

func (b *Writer) Available() int

Available返回buffer中有多少的字节数未使用。

(4). func (*Writer) Buffered

func (b *Writer) Buffered() int

Buffered返回已经写入到当前缓存的字节数。

(5). func (*Writer) Flush

func (b *Writer) Flush() error

Flush将缓存上的所有数据写入到底层的io.Writer中。

(6). func (*Writer) ReadFrom

func (b *Writer) ReadFrom(r io.Reader) (n int64, err error)

ReadFrom实现了io.ReaderFrom。

(7). func (*Writer) Reset

func (b *Writer) Reset(w io.Writer)

Reset将丢弃任何未刷新的缓冲数据,清除任何错误,并重置b以将其输出写入w。

(8). func (*Writer) Write

func (b *Writer) Write(p []byte) (nn int, err error)

Writer将p中的内容写入到缓存中。 它返回写入的字节数。 如果nn < len§, 它也会返回错误,用于解释为什么写入的数据会短缺。

(9). func (*Writer) WriteByte

func (b *Writer) WriteByte(c byte) error

WriterByte写单个字节。

(10). func (*Writer) WriteRune

func (b *Writer) WriteRune(r rune) (size int, err error)

WriteRune写单个的Unicode代码,返回写的字节数,和遇到的错误。

(11). func (*Writer) WriteString

func (b *Writer) WriteString(s string) (int, error)

WriteString写一个string。 它返回写入的字节数。 如果字节数比len(s)少,它就会返回error来解释为什么写入的数据短缺了。

3.12. 包文件

bufio.go

scan.go

 类似资料: