『GCTT 出品』Go 语言 bufio 包的介绍
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 intfunc (*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/O1111Buffered I/O31
没有被缓存的 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 intfunc (*Writer) Write(p []byte) (n int, err error) {
fmt.Printf("Write: %qn", 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 intfunc (*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())
}01231
n
从 0 开始,当有数据被添加到缓存中时,该数据的长度值将会被加和到 n
中(操作位置向后移动)。当bw.Write([] byte{'d'})
被调用时,flush会被触发,n
会被重设为0。
Large writes
type Writer intfunc (*Writer) Write(p []byte) (n int, err error) {
fmt.Printf("%qn", 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 intfunc (*Writer1) Write(p []byte) (n int, err error) {
fmt.Printf("writer#1: %qn", p) return len(p), nil}type Writer2 intfunc (*Writer2) Write(p []byte) (n int, err error) {
fmt.Printf("writer#2: %qn", 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())2101
写{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 bytesfmt.Println(bw.Buffered())
bw.WriteString("aa")
fmt.Println(bw.Buffered())0135
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 intfunc (*Writer) Write(p []byte) (n int, err error) {
fmt.Printf("%qn", 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("%qn", 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("%qn", b)
r.Read(make([]byte, 16))
r.Read(make([]byte, 15))
fmt.Printf("%qn", 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("%qn", b)
s2 := strings.NewReader("efgh")
r.Reset(s2)
_, err = r.Read(b)if err != nil { panic(err)
}
fmt.Printf("%qn", 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("%qn", buf)
br.Discard(4)
br.Read(buf)
fmt.Printf("%qn", 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("%qn", buf)
br.Discard(13)
fmt.Println("Discard")
br.Read(buf)
fmt.Printf("%qn", 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.Reader
的 Read
方法从底层的 io.Reader
中一次读取最大的数量:
- 如果内部缓存具有至少一个字节的数据,那么无论传入的切片的大小(
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 = %dn", buf[:n], n)
}
Read
read = "cd", n = 2
我们的 io.Reader
实例无线返回「abcd」(不会返回 io.EOF
)。 第二次调用 Read
并传入长度为4的切片,但是内部缓存在第一次从 io.Reader
中读取数据之后已经具有数据「cd」,所以 bufio.Reader
返回缓存中的数据数据,而不和底层 reader 进行通信。
- 如果内部缓存是空的,那么将会执行一次从底层 io.Reader 的读取操作。 从前面的例子中我们可以清晰的看到如果我们开启了一个空的缓存,然后调用:
n, err := br.Read(buf)
将会触发读取操作来填充缓存。
- 如果内部缓存是空的,但是传入的切片长度大于缓存长度,那么
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 = %dn", buf[:n], n)
fmt.Printf("buffered = %dn", br.Buffered())
}
Read
read = "aaaaaaaaaaaaaaaaa", n = 17buffered = 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("%qn", byte)
fmt.Printf("buffered = %dn", br.Buffered())
err = br.UnreadByte()if err != nil { panic(err)
}
fmt.Printf("buffered = %dn", br.Buffered())byte, err = br.ReadByte()if err != nil { panic(err)
}
fmt.Printf("%qn", byte)
fmt.Printf("buffered = %dn", br.Buffered())'a'buffered = 3buffered = 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: %qn", 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: %qn", 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: %qn", 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
或者 rn
)。 此方法的签名不同于 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: %tn", token, isPrefix)
token, isPrefix, err = r.ReadLine()if err != nil { panic(err)
}
fmt.Printf("Token: %q, prefix: %tn", token, isPrefix)
token, isPrefix, err = r.ReadLine()if err != nil { panic(err)
}
fmt.Printf("Token: %q, prefix: %tn", token, isPrefix)
token, isPrefix, err = r.ReadLine()if err != nil { panic(err)
}
Token: "aaaaaaaaaaaaaaaa", prefix: trueToken: "aaaa", prefix: falseToken: "b", prefix: falsepanic: 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: %tn", token, isPrefix)
s = strings.NewReader("abcn")
r.Reset(s)
token, isPrefix, err = r.ReadLine()if err != nil { panic(err)
}
fmt.Printf("Token: %q, prefix: %tn", token, isPrefix)
Token: "abc", prefix: falseToken: "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 #%dn", 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: %dn", n)
}
Read #0Read #1Read #2Read #3Read #4Read #5Read #6Read #7Read #8Read #9Read #10Written bytes: 40
bufio.Scanner
go语言中对bufio.Scanner的深层分析
ReadBytes(‘n’), ReadString(‘n’), ReadLine 还是 Scanner?
就像前面说的那样,ReadString('n')
只是对于 ReadBytes(
n)
的简单封装。 所以让我们来讨论一下另外三者之间的不同之处吧。
- ReadBytes 不会自动处理
rn
序列:
s := strings.NewReader("arnb")
r := bufio.NewReader(s)for {
token, _, err := r.ReadLine() if len(token) > 0 {
fmt.Printf("Token (ReadLine): %qn", token)
} if err != nil { break
}
}
s.Seek(0, io.SeekStart)
r.Reset(s)for {
token, err := r.ReadBytes('n')
fmt.Printf("Token (ReadBytes): %qn", token) if err != nil { break
}
}
s.Seek(0, io.SeekStart)
scanner := bufio.NewScanner(s)for scanner.Scan() {
fmt.Printf("Token (Scanner): %qn", scanner.Text())
}
Token (ReadLine): "a"Token (ReadLine): "b"Token (ReadBytes): "arn"Token (ReadBytes): "b"Token (Scanner): "a"Token (Scanner): "b"
*ReadBytes* 会将分隔符一起返回,所以需要额外的一些工作来重新处理数据(除非返回分隔符是有用的)。
- ReadLine 不会处理超出内部缓存的行:
s := strings.NewReader(strings.Repeat("a", 20) + "n")
r := bufio.NewReaderSize(s, 16)
token, _, _ := r.ReadLine()
fmt.Printf("Token (ReadLine): t%qn", token)
s.Seek(0, io.SeekStart)
r.Reset(s)
token, _ = r.ReadBytes('n')
fmt.Printf("Token (ReadBytes): t%qn", token)
s.Seek(0, io.SeekStart)
scanner := bufio.NewScanner(s)
scanner.Scan()
fmt.Printf("Token (Scanner): t%qn", scanner.Text())
Token (ReadLine): "aaaaaaaaaaaaaaaa"Token (ReadBytes): "aaaaaaaaaaaaaaaaaaaan"Token (Scanner): "aaaaaaaaaaaaaaaaaaaa"
为了取回流中剩余的数据,*ReadLine* 需要被调用两次。 被 Scanner 处理的最大 token 长度为 64*1024。 如果传入更长的 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): %dn", len(token))
fmt.Printf("Error (ReadLine): %vn", err)
s.Seek(0, io.SeekStart)
r.Reset(s)
token, err = r.ReadBytes('n')
fmt.Printf("Token (ReadBytes): %dn", len(token))
fmt.Printf("Error (ReadBytes): %vn", err)
s.Seek(0, io.SeekStart)
scanner := bufio.NewScanner(s)
scanner.Scan()
fmt.Printf("Token (Scanner): %dn", len(scanner.Text()))
fmt.Printf("Error (Scanner): %vn", scanner.Err())
Token (ReadLine): 4096Error (ReadLine): <nil>
Token (ReadBytes): 65537Error (ReadBytes): <nil>
Token (Scanner): 0Error (Scanner): bufio.Scanner: token too long
</nil></nil>
- 就像上面那样,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。 它由 Reader 和 Writer 构成:
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("%qn", 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 中文网 荣誉推出
本文由 GCTT 原创翻译,Go语言中文网首发。也想加入译者行列,为开源做一些自己的贡献么?欢迎加入 GCTT!
翻译工作和译文发表仅用于学习和交流目的,翻译工作遵照 CC-BY-NC-SA 协议规定,如果我们的工作有侵犯到您的权益,请及时联系我们。
欢迎遵照 CC-BY-NC-SA 协议规定 转载,敬请在正文中标注并保留原文/译文链接和作者/译者等信息。
文章仅代表作者的知识和看法,如有不同观点,请楼下排队吐槽