文章目录
ringbuffer
,是环形缓存区, 或叫环形队列。
不同于一般常见的队列,环形队列收尾相连,通过移动指针来控制队列中内容的读写。
这样做有什么好处呢?
最大的好处是环形队列出队(读取)后,不需要对后续队列内容进行搬移,可以后续由入队(写入)覆盖。
下面来看下一种常见的实现方式, 通过读写指针计数来实现环形队列
计数法实现
先过一遍代码,后边会有图解版说明,方便理解
代码参考自鸟窝无限缓存 channel-chanx
数据结构如下:
1 | type T interface{} |
读取时:
1 | // 读写指针相遇为空 |
写入时:
1 | func (r *RingBuffer) Write(v T) { |
具体扩容:
1 | func (r *RingBuffer) grow() { |
为方便理解,具体举例如下图所示,对照看应该很容易理解:
除此外,还有一种镜像指示位方法实现环形队列
镜像指示位实现
缓冲区的长度如果是 n,逻辑地址空间则为 0 至 n-1;那么,规定 n 至 2n-1 为镜像逻辑地址空间。本策略规定读写指针的地址空间为 0 至 2n-1,其中低半部分对应于常规的逻辑地址空间,高半部分对应于镜像逻辑地址空间。当指针值大于等于 2n 时,使其折返(wrapped)到 ptr-2n。使用一位表示写指针或读指针是否进入了虚拟的镜像存储区:置位表示进入,不置位表示没进入还在基本存储区。
from wiki
大意就是在计数可以允许绕两圈(2n-1
),假定写指针一圈后和读指针相遇
原来绕一圈归零后相遇(r == w
)为队列已满。
现在绕一圈后(未归零)相遇(w == (r + n)
)为队列已满。
如下图:
而且如果队列长度刚好是 2 的幂,则可以利用位运算来判断差值是否为n
:
1 | func (r *RingBuffer) IsFull() bool { |
然后读写指针的位置获取和偏移也可以按位与来替代取模运算
1 | // 位置 |
代码详见ringbuffer-mirror, 感兴趣可以去尝试下。
如有疑问,请文末留言交流或邮件:newbvirgil@gmail.com 本文链接 : https://newbmiao.github.io/2021/09/01/how-to-build-ringbuffer.html