一、介绍

BytesQueue结构,是bigcache真正数据存储的地方。

值得注意的是删除缓存元素的时候bigcache只是在map[uint64]uint32中删除了它的索引,byte数组里的空间是不会释放的。

在 bigCache 中,所有的 value 都是存在一个 BytesQueue 中的,从实现可知,所有的用户存储数据经由序列化后存入 array []byte

// BytesQueue is a non-thread safe queue type of fifo based on bytes array.
// BytesQueue 是基于字节数组的非线程安全队列类型的FIFO。
// For every push operation index of entry is returned. It can be used to read the entry later
// 对于每个推送操作索引,都会返回。它可用于稍后阅读条目。
type BytesQueue struct {
    full         bool
    array        []byte // 真正存储数据的地方
    capacity     int    // array 的容量
    maxCapacity  int    // array 可申请的最大容量
    head         int
    tail         int // 下次可以插入 item 的位置
    count        int // 当前插入的 item 数量
    rightMargin  int
    headerBuffer []byte // 插入前做临时 buffer 所用(slice-copy)
    verbose      bool   // 打印 log 开关
}

初始化BytesQueue的方法为

func NewBytesQueue(capacity int, maxCapacity int, verbose bool) *BytesQueue {
    return &BytesQueue{
        array:        make([]byte, capacity), // 真正存储数据的地方,长度为capacity,直接初始化每个值
        capacity:     capacity,
        maxCapacity:  maxCapacity,
        headerBuffer: make([]byte, binary.MaxVarintLen32),
        tail:         leftMarginIndex,
        head:         leftMarginIndex,
        rightMargin:  leftMarginIndex,
        verbose:      verbose,
    }
}

我们通过维护下面几个变量来实现存储位移及标识:
head:起始位置(也可以理解为,当前最老的数据的位置,删除的逻辑从这个位置开始)
tail:下次可以插入 item 的位置
capacity:标识 array 的容量
count:当前已经插入的 item 的数量
maxCapacity:标识 array 可以申请的最大容量
rightMargin:用于标识队列中最后一个元素的位置,是一个绝对位置。
leftMarginIndex:常量,值为 1,标识队列的开头位置(0 号不用)

注意, head 和 tail 以及 rightMargin 的初始值都是 leftMarginIndex。BytesQueue 使用 []byte 类型来模拟队列,插入数据从 tail 位置,删除数据从 head 位置。为标准的FIFO队列。
image.png

二、如何使用这个BytesQueue
1、插入item到队列,通过调用BytesQueue.Push([]byte) 方法,我们可以把[]byte类型的数据插入到BytesQueue中。返回为这个值存储的index索引。

func TestQueuePush(t *testing.T) {
    // 初始化一个byte队列
    queue := NewBytesQueue(5, 0, false)
    t.Log(queue) // &{false [0 0 0 0 0] 5 0 1 1 0 1 [0 0 0 0 0] false}
    // 调用Push方法,会返回获取这个值的index索引
    index, err := queue.Push([]byte("a"))
    t.Log(index, err) // 1 <nil>
    index, err = queue.Push([]byte("b"))
    t.Log(index, err) // 3 <nil>

    // 通过index索引就可以获取到这个值
    a, err2 := queue.Get(1)
    t.Log(string(a), err2) // a <nil>
    b, err2 := queue.Get(3)
    t.Log(string(b), err2) // b <nil>
}

这样,我们就相当于一个值,和一个索引index对应了。
通过一个索引可以快速的获取到这个值。
bigcache我们通过BytesQueue,存储数据。
再用一个map,记录一下这个index和值的 对应关系。
就可O(1)的时间复杂度,查询BytesQueue的所有数据。

-----注意:为什么通过index可以获取value的值?
因为我们再push的 []byte的时候,最终存储这个[]byte会用一个8字节存储这个entry的长度。这样通过index我们获取到这个长度,然后就可以获取到这个数据。

func (q *BytesQueue) Push(data []byte) (int, error) {
    neededSize := getNeededSize(len(data))
    ....... // 省略
    index := q.tail

    q.push(data, neededSize)

    return index, nil
}

从Push()方法中,我们看到调用了一个push()方法。我们打开源代码,
可以看到最终在保存数据的时候,先用一个8字节保存了 data的长度。

func (q *BytesQueue) push(data []byte, len int) {
    headerEntrySize := binary.PutUvarint(q.headerBuffer, uint64(len))

    q.copy(q.headerBuffer, headerEntrySize) // 用一个8字节保存data的长度
    q.copy(data, len-headerEntrySize)       // 写入data

    if q.tail > q.head {
        q.rightMargin = q.tail
    }
    if q.tail == q.head {
        q.full = true
    }

    q.count++
}

byteQueue中每个元素都有2部分组成,前8个byte是数据的长度,后面是数据的值本身,每个byteQueue中每个元素的最大长度是8个字节,2的64次方。


海生
104 声望32 粉丝

与黑夜里,追求那一抹萤火。