Go语言核心36讲(Go语言实战与应用十六)--学习笔记

38 | bytes包与字节串操作(上) 前导内容: bytes.Buffer基础知识

strings包和bytes包可以说是一对孪生兄弟,它们在 API 方面非常的相似。单从它们提供的函数的数量和功能上讲,差别可以说是微乎其微。

只不过,strings包主要面向的是 Unicode 字符和经过 UTF-8 编码的字符串,而bytes包面对的则主要是字节和字节切片。

我今天会主要讲bytes包中最有特色的类型Buffer。顾名思义,bytes.Buffer类型的用途主要是作为字节序列的缓冲区。

与strings.Builder类型一样,bytes.Buffer也是开箱即用的。

但不同的是,strings.Builder只能拼接和导出字符串,而bytes.Buffer不但可以拼接、截断其中的字节序列,以各种形式导出其中的内容,还可以顺序地读取其中的子序列。

可以说,bytes.Buffer是集读、写功能于一身的数据类型。当然了,这些也基本上都是作为一个缓冲区应该拥有的功能。

在内部,bytes.Buffer类型同样是使用字节切片作为内容容器的。并且,与strings.Reader类型类似,bytes.Buffer有一个int类型的字段,用于代表已读字节的计数,可以简称为已读计数。

不过,这里的已读计数就无法通过bytes.Buffer提供的方法计算出来了。

我们先来看下面的代码

var buffer1 bytes.Buffer contents := "Simple byte buffer for marshaling data." fmt.Printf("Writing contents %q ...\n", contents) buffer1.WriteString(contents) fmt.Printf("The length of buffer: %d\n", buffer1.Len()) fmt.Printf("The capacity of buffer: %d\n", buffer1.Cap())

我先声明了一个bytes.Buffer类型的变量buffer1,并写入了一个字符串。然后,我想打印出这个bytes.Buffer类型的值(以下简称Buffer值)的长度和容量。在运行这段代码之后,我们将会看到如下的输出:

Writing contents "Simple byte buffer for marshaling data." ... The length of buffer: 39 The capacity of buffer: 64

乍一看这没什么问题。长度39和容量64的含义看起来与我们已知的概念是一致的。我向缓冲区中写入了一个长度为39的字符串,所以buffer1的长度就是39。

根据切片的自动扩容策略,64这个数字也是合理的。另外,可以想象,这时的已读计数的值应该是0,这是因为我还没有调用任何用于读取其中内容的方法。

可实际上,与strings.Reader类型的Len方法一样,buffer1的Len方法返回的也是内容容器中未被读取部分的长度,而不是其中已存内容的总长度(以下简称内容长度)。示例如下:

p1 := make([]byte, 7) n, _ := buffer1.Read(p1) fmt.Printf("%d bytes were read. (call Read)\n", n) fmt.Printf("The length of buffer: %d\n", buffer1.Len()) fmt.Printf("The capacity of buffer: %d\n", buffer1.Cap())

当我从buffer1中读取一部分内容,并用它们填满长度为7的字节切片p1之后,buffer1的Len方法返回的结果值也会随即发生变化。如果运行这段代码,我们会发现,这个缓冲区的长度已经变为了32。

另外,因为我们并没有再向该缓冲区中写入任何内容,所以它的容量会保持不变,仍是64。

总之,在这里,你需要记住的是,Buffer值的长度是未读内容的长度,而不是已存内容的总长度。 它与在当前值之上的读操作和写操作都有关系,并会随着这两种操作的进行而改变,它可能会变得更小,也可能会变得更大。

而Buffer值的容量指的是它的内容容器(也就是那个字节切片)的容量,它只与在当前值之上的写操作有关,并会随着内容的写入而不断增长。

再说已读计数。由于strings.Reader还有一个Size方法可以给出内容长度的值,所以我们用内容长度减去未读部分的长度,就可以很方便地得到它的已读计数。

然而,bytes.Buffer类型却没有这样一个方法,它只有Cap方法。可是Cap方法提供的是内容容器的容量,也不是内容长度。

并且,这里的内容容器容量在很多时候都与内容长度不相同。因此,没有了现成的计算公式,只要遇到稍微复杂些的情况,我们就很难估算出Buffer值的已读计数。

一旦理解了已读计数这个概念,并且能够在读写的过程中,实时地获得已读计数和内容长度的值,我们就可以很直观地了解到当前Buffer值各种方法的行为了。不过,很可惜,这两个数字我们都无法直接拿到。

虽然,我们无法直接得到一个Buffer值的已读计数,并且有时候也很难估算它,但是我们绝对不能就此作罢,而应该通过研读bytes.Buffer和文档和源码,去探究已读计数在其中起到的关键作用。

否则,我们想用好bytes.Buffer的意愿,恐怕就不会那么容易实现了。

下面的这个问题,如果你认真地阅读了bytes.Buffer的源码之后,就可以很好地回答出来。

我们今天的问题是:bytes.Buffer类型的值记录的已读计数,在其中起到了怎样的作用?

这道题的典型回答是这样的。

bytes.Buffer中的已读计数的大致功用如下所示。

读取内容时,相应方法会依据已读计数找到未读部分,并在读取后更新计数。

写入内容时,如需扩容,相应方法会根据已读计数实现扩容策略。

截断内容时,相应方法截掉的是已读计数代表索引之后的未读部分。

读回退时,相应方法需要用已读计数记录回退点。

重置内容时,相应方法会把已读计数置为0。

导出内容时,相应方法只会导出已读计数代表的索引之后的未读部分。

获取长度时,相应方法会依据已读计数和内容容器的长度,计算未读部分的长度并返回。

问题解析

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwsxgz.html