逐步学习Go-bytes.Buffer源码关键点学习

内容纲要

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

流程:

  1. 先尝试不开申请内存空间来扩容(判断当前buffer的剩余容量还够)
  2. 如果够就直接返回
  3. 如果不够就判断要扩容的大小是否小于smallBufferSize(64个字节)并且是否切片为空(创建后尚未分配内存空间)
  4. 如果小于就创建一个等于smallBufferSize的切片后返回(创建后尚未分配内存空间)
  5. 第一种不重新分配内存空间:看一下读偏移和剩余的容量/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
}

smallBufferSize

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部