环形缓冲 - 一种 处理发送与接收的数据流 的数据结构

什么是环形缓冲?

一种有效管理固定大小缓冲区的数据结构,在需要连续写入和读取数据的场景中特别有用,例如在是实时系统或者流应用程序中。

1.环形缓冲的抽象结构(便于理解,与真实情况有所偏差):

1.一块连续的,首尾相接的内存

(可以想象成一个圆环)

2.一个读指针

指向“环”里最早写入、尚未被取走的那个字节位置,每次读操作后向前移动。

3.一个写指针

指向“环”里下一个可写入的空位,每次写操作后向前移动。

4.容量标记

用来判断“环”里剩余空间或已用空间,防止读写指针越界;通常把容量取成 2 的幂,可用掩码替代取模运算。

2.环形缓冲的读写流程

**缓冲区开始位置 **和 缓冲区结束位置(或空间大小) 实际上定义了环形缓冲区的实际逻辑空间和大小。

读索引写索引 标记了缓冲区进行读操作和写操作时的具体位置。

1.当环形缓冲区为空时,读索引和写索引指向相同的位置(因为是环形缓冲区,可以出现在任何位置);
2.当向缓冲区写入一个元素时,元素A被写入写索引当前所指向位置,然后写索引加1,指向下一个位置;
3.当再写如一个元素B时,元素B继续被写入写索引当前所指向位置,然后写索引加1,指向下一个位置;
4.当接着写入CDEFG五个元素后,缓冲区就满了,这时写索引写索引指向同一个位置(和缓冲区为空时一样);
5.当从缓冲区中读出一个元素A时,读索引当前所在位置的元素被读出,然后读索引加1,指向下一个位置;
6.继续读出元素B时,还是读索引当前所在位置的元素被读出,然后读索引加1,指向下一个位置。

三、缓冲区时的两种处理策略

当环形缓冲区满时,可以选择两种处理策略:

  • 覆盖老数据:新写入的数据覆盖最旧的数据。这种策略适用于实时流数据处理(如音频、视频流),其中丢失少量数据对系统的影响较小。
  • 抛出异常:如果缓冲区满时不允许覆盖数据,则会抛出异常,提示缓冲区已满。这种策略适用于任务调度、消息队列等应用,确保数据的完整性和准确性。

四、环形缓冲区的设计要点

1.FIFO原则:环形缓冲区实现的是先进先出(FIFO)原则,数据的读写遵循这个顺序。读数据时,一定要读出缓冲区中最老的数据。。写就相当于进,读就相当于出。所以读数据时,一定要保证读最老的数据。一般的情况下不会有问题,但有一种场景需要小心。如上图所示环形缓冲区的大小为 七,缓冲区中已经存储了7,8,9,3,4五个元素。如果再向缓冲区中写入三个元素ABC,因为剩余空间为2了,所以要想写入这三个元素肯定会覆盖掉一个元素。当缓冲区是满的时候,继续写入元素(覆盖),除了写索引要变,读索引也要跟着变,保证读索引一定是指向缓冲区中最老的元素。(类似于快慢指针,快指针一定要领先慢指针)

2.循环结构:缓冲区的读取和写入操作是循环的。当写索引或读索引达到缓冲区的末尾时,它们会回绕到缓冲区的起始位置。

3.缓冲区状态判断:判断缓冲区是否为空或满时,通常会遇到写索引和读索引相同的情况。此时,通过额外的“镜像指示位”来区分缓冲区是空还是满。

五、镜像指示位策略

为了有效判断环形缓冲区是否满或空,可以采用镜像指示位(Mirrored Flag)策略。具体做法是:

  • 缓冲区的逻辑地址空间为0到n-1,其中n是缓冲区的大小。
  • 镜像空间从n到2n-1,通过读取指针和写入指针的指示位,判断缓冲区的状态。

具体来说:

  • 空缓冲区:读索引和写索引指示位相同。
  • 满缓冲区:读索引和写索引指示位不同。