go内存缓存BigCache实现BytesQueue源码解读
作者:海生
一、BytesQueue结构
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队列。
二、如何使用这个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次方。
以上就是go内存缓存BigCache实现BytesQueue源码解读的详细内容,更多关于go内存缓存BigCache BytesQueue的资料请关注脚本之家其它相关文章!