|
|
**RingBuffer**, 这种结构实现起来只需要几行代码即可,但使用场景却很广泛,比如在Linux内核中网络数据包的缓存,系统日志的存储等多处使用过该结构。同时它也被广泛的应用于异步通信以及嵌入式设备中,提供高效的数据缓存读写操作。
|
|
|
|
|
|
# 一、原理
|
|
|
RingBufferr实现比较简单,基本上只需要一个数组结构,外加两个用于存储位置信息的变量即可。其中的数组采用固定大小容量,便于重用内存,不会出现动态内存不断分配和销毁的情况,这对于一些GC类编程语言来说,大幅减少了内存管理的成本。
|
|
|
下面就是一个典型的RingBuffer图例,包含一个容量大小为6的数组,以及一个读和写指针。其中Write和Read用于管理读写的位置序列,读写开始后,不断增加该值来定位下一次的读写位置。(实际上由3个参数控制,读指针、写指针、容量)
|
|
|
![[aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9FSDlId2ljaHd6NVlpYVZoeWxzcWhWS05NaWJ0SkFpYlR1T2VYNFB5aWJQMU03TmRRd292TVZiVUxjZjVtckhqRHJ4dk1FbFdmd3dGaWE2TjZwTzZ4M0V6dXVVdy82NDA.png]]
|
|
|
|
|
|
每次写数据:在Write对应位置写入新值,并向前移动对应的Write指针位置,如果遇到指针已经处于尾部,则移动到最开始位置,形成一个环形, 类似于双向链表。
|
|
|
每次读取数据:在Read位置读取当前值,并移动Read位置,同样如果遇到已经到达尾部,则返回到最开始的初始位置。
|
|
|
整个数据流的读写过程就是通过不断的操作Write和Read来实现数据的高效处理。
|
|
|
|
|
|
# 二、读写操作实例
|
|
|
通过一个简单的实例来介绍如何操作RingBuffer来管理数据: 首先初始化一个空的数组,并设置Read=0和Write=0, 图例中黄色代表已写入的数据,绿色代表已读取的数据,红色代表异常情况:
|
|
|
(1) 写入三个元素分别是:1,2,3, 这时候读指针位置不变,写指针移动三个位置到索引为3的位置(数组索引位置从0开始)
|
|
|
![[aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9FSDlId2ljaHd6NVlpYVZoeWxzcWhWS05NaWJ0SkFpYlR1T2VYNFB5aWJQMU03TmRRd292TVZiVUxjZjVtckhqRHJ4dk1FbFdmd3dGaWE2TjZwTzZ4M0V6dXVVdy82NDA 1.png]]
|
|
|
|
|
|
(2)读取一个元素,读指针移动一个位置,写指针不变,获取数据值1
|
|
|
![[aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9FSDlId2ljaHd6NVlpYVZoeWxzcWhWS05NaWJ0SkFpYlR1T2VYNFB5aWJQMU03TmRRd292TVZiVUxjZjVtckhqRHJ4dk1FbFdmd3dGaWE2TjZwTzZ4M0V6dXVVdy82NDA 2.png]]
|
|
|
|
|
|
(3)继续写入四个元素,分别是:4,5,6,7, 其中4,5,6分别放入剩余的数组空缺中,但是7 由于已经没有位置可写,则从0开始覆盖原有写入1的位置。注意这里我们没有设置write=0,而是直接在原Write值上继续加1,我们取模size即可获得新的写入位置(取模后重新返回头部)。
|
|
|
![[aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9FSDlId2ljaHd6NVlpYVZoeWxzcWhWS05NaWJ0SkFpYlR1T2VYNFB5aWJQMU03TmRRd292TVZiVUxjZjVtckhqRHJ4dk1FbFdmd3dGaWE2TjZwTzZ4M0V6dXVVdy82NDA 3.png]]
|
|
|
|
|
|
(4)当我们再次写入两个值:8,9的时候,由于与上一轮的Read发生了交叉,为了保证前后读写的顺序性,**我们需要同时移动读指针的位置,使得读位置总是指向最旧的数据**。
|
|
|
![[aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9FSDlId2ljaHd6NVlpYVZoeWxzcWhWS05NaWJ0SkFpYlR1T2VYNFB5aWJQMU03TmRRd292TVZiVUxjZjVtckhqRHJ4dk1FbFdmd3dGaWE2TjZwTzZ4M0V6dXVVdy82NDA 4.png]]
|
|
|
|
|
|
(5)这时候如果读取两个数据,则读指针只需要按照当前序列向前移动两个位置即可,分别获得值4,5 代表了最早的数据项,假如上面我们没有移动读指针,则读取的可能会是最新数据。
|
|
|
![[aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9FSDlId2ljaHd6NVlpYVZoeWxzcWhWS05NaWJ0SkFpYlR1T2VYNFB5aWJQMU03TmRRd292TVZiVUxjZjVtckhqRHJ4dk1FbFdmd3dGaWE2TjZwTzZ4M0V6dXVVdy82NDA 5.png]]
|
|
|
|
|
|
(6)如果我们这时候读取速度加快,假如读取5个值,可成功读取6,7,8,9,当读取到4值的时候由于此时,读写位置重叠(读数据不能超过写数据的位置,否则重复读取的问题),无法进一步读取数据。退出读取流程。
|
|
|
![[aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9FSDlId2ljaHd6NVlpYVZoeWxzcWhWS05NaWJ0SkFpYlR1T2VYNFB5aWJQMU03TmRRd292TVZiVUxjZjVtckhqRHJ4dk1FbFdmd3dGaWE2TjZwTzZ4M0V6dXVVdy82NDA 6.png]] |