bytes.Buffer 结构体
type Buffer struct {
// 切片存储数据
buf []byte
// 读偏移
off int
// todo
lastRead readOp
}
看到结构体就比较清晰了,使用切片来存储数据,有一个读偏移用于记录读取的偏移,同时有一个lastRead用来标记上次读取操作的类型。
在bytes.Buffer结构体中,只有lastRead这个不好理解,乍一看,乍二看也看不懂。我们来分析下:
lastRead分析
首先我们来看下它的类型:
// The readOp constants describe the last action performed on
// the buffer, so that UnreadRune and UnreadByte can check for
// invalid usage. opReadRuneX constants are chosen such that
// converted to int they correspond to the rune size that was read.
type readOp int8
这个详细注释了readOp类型,首先它是单字节有符号的int,它用来记录在Buffer上的最后一次操作类型,然后UnreadRune和UnreadByte就可以来时间是否存在无效使用。
什么是无效的使用?一般是指未进行过读操作,或者上一次操作不是读操作就尝试使用取消读操作的方法。
我看到这儿还是不懂啊,接下来再看下几个常量:
const (
opRead readOp = -1 // Any other read operation.
opInvalid readOp = 0 // Non-read operation.
opReadRune1 readOp = 1 // Read rune of size 1.
opReadRune2 readOp = 2 // Read rune of size 2.
opReadRune3 readOp = 3 // Read rune of size 3.
opReadRune4 readOp = 4 // Read rune of size 4.
)
这里面有一个Rune,什么是rune?
builtin.go中定义了rune:
// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32
根据注释我们可以知道rune是从整数中来识别字符。
所以这个rune是为了表示并操作unicode字符集定义和设计的。
那这样我们也能理解opReadRune1-4就是unicode字符集中的1-4个字节长度的编码。
我们来试试?我们先写入一个中文字符串,比如:“小厂程序员”,然后应该怎么读取出来?
我们可以调用ReadRune从当前读取偏移中读取一个unicode字符串出来。
func TestBuffer_ShouldGetXiao_WhenWriteUnicodeStringAndGetFirstCharacter(t *testing.T) {
buf := new(bytes.Buffer)
str := "小厂程序员"
buf.Write([]byte(str))
r, _, err := buf.ReadRune()
if err != nil {
panic(err)
}
assert.Equal(t, '小', r)
}
全部读取出来就调用String()方法,String()方法会将buffer中剩余的内容读取出来并转成字符串:
func TestBuffer_ShouldGetAll_WhenWriteUnicodeStringAndCallReadString(t *testing.T) {
buf := new(bytes.Buffer)
str := "小厂程序员"
buf.Write([]byte(str))
nstr := buf.String()
assert.Equal(t, nstr, nstr)
}
空Buffer扩容
以下创建bytes.Buffer后Buffer还没有为内容分配内存空间,等你第一次调用Write后会为Buffer分配内存空间:
buf := new(bytes.Buffer)
buf := bytes.Buffer{}
Write触发分配内存空间(grow)
流程: tryGrowByReslice -> grow -> copy。
tryGrouByReslice
这段源码比较简单,先判断切片剩余存储空间是否够用,够用就返回当前内容长度和不用扩容标志。
返回长度的目的是为了将写入的byte数组拷贝的时候能找找到正确起始偏移。
func (b *Buffer) tryGrowByReslice(n int) (int, bool) {
if l := len(b.buf); n <= cap(b.buf)-l {
b.buf = b.buf[:l+n]
return l, true
}
return 0, false
}
容量不够的话,就要真正重新申请内存空间来扩容了。
grow
流程:
- 先尝试不开申请内存空间来扩容(判断当前buffer的剩余容量还够)
- 如果够就直接返回
- 如果不够就判断要扩容的大小是否小于smallBufferSize(64个字节)并且是否切片为空(创建后尚未分配内存空间)
- 如果小于就创建一个等于smallBufferSize的切片后返回(创建后尚未分配内存空间)
- 第一种不重新分配内存空间:看一下读偏移和剩余的容量/2是不是满足新容量,如果满足就直接覆盖以前读过的内容
func (b *Buffer) grow(n int) int {
m := b.Len()
// Try to grow by means of a reslice.
if i, ok := b.tryGrowByReslice(n); ok {
return i
}
if b.buf == nil && n <= smallBufferSize {
b.buf = make([]byte, n, smallBufferSize)
return 0
}
c := cap(b.buf)
if n <= c/2-m {
// We can slide things down instead of allocating a new
// slice. We only need m+n <= c to slide, but
// we instead let capacity get twice as large so we
// don't spend all our time copying.
copy(b.buf, b.buf[b.off:])
} else if c > maxInt-c-n {
panic(ErrTooLarge)
} else {
// Add b.off to account for b.buf[:b.off] being sliced off the front.
b.buf = growSlice(b.buf[b.off:], b.off+n)
}
// Restore b.off and len(b.buf).
b.off = 0
b.buf = b.buf[:m+n]
return m
}