环形缓冲区(Circular Buffer),又被称为环形队列或循环缓冲区,是一种常用的数据结构,特别适用于缓冲流数据的场景,如硬件设备的数据交换、网络通信等
1. 什么是环形缓冲区
环形缓冲区是一个先进先出(FIFO)的闭环的存储空间。相当于就是一个固定大小的数组,和两个指针,读指针和写指针。环形 缓冲区的”环形“特性意味着当任一指针到达数组的末端的时候,会自动回绕到数据开始的位置。
2. 环形缓冲区读写指针
- 写指针(head): 指向环形缓冲区下一个数据将要写入的位置 , 当有新的数据写入缓冲区的时候,数据会被存储在写指针指向的位置,然后写指针向后移动。如果移动到数组的末尾,并且还有数据要写入的时候,回绕回到数组的开头,继续写入数据。
- 读指针(tail) :指向下一个将要被读取的数组的位置,和写指针类似,如果读指针度到了数组的末尾,还有数据要读取,它回绕回到数组的开头,继续读取数据。
3. 环形缓冲区读写区域
- 当写指针(head)在读指针(tail)前面的时候,表示数据是连续的。可以直接从读指针位置开始连续读取数据,如图
- 当读指针(tail) 在写指针前面的时候(head),表示数据跨越了缓冲区的末尾回到开头
4. 当数据读写超出区域时
4.1 数据写入超出可写区域时
- 等待: 如果没有足够的空间写入新数据,可以等待读取更多的数据,释放空间
- 覆盖旧数据: 如果最新的数据比旧数据更重要,可以选择覆盖旧数据。这通常在数据流应用中使用,比如实时视频流处理。
- 拒绝写入: 直接拒绝写入操作,并可能返回一个错误,告诉调用者缓冲区已满。
- 动态扩容: 如果实现允许,可以动态增加缓冲区的大小以容纳更多数据。这可能需要重新分配内存和移动现有数据,从而增加复杂性和性能开销
假设一个大小为10的环形缓冲区,当前写指针在位置7,读指针在位置2,可写区域是3、4、5、6(4个位置),但你想写入5个字节的数据。
如果选择覆盖,那么从位置3开始的5个字节会写入,这将导致位置2的数据被覆盖(在实际应用中需要小心处理读写指针)。
4.2 数据读取超出可读区域
- 部分读取: 只读取可读区域内的数据,并返回实际读取的长度。
- 等待: 如果缓冲区中的数据不足,可以等待直到有足够的数据被写入。
- 返回错误: 如果必须一次性读取足够的数据,不足时返回错误。
5. 代码
主要的就是读取环形缓冲区部分的代码
5.1 定义数据结构
struct ring_buffer
{
unsigned char * buf ;
int buf_size ;
int buf_head ;
int buf_tail ;
int buf_data_len ;
} ;
5.2 对其初始化
int ring_buffer_init(struct ring_buffer * ringbuf,int buf_size)
{
if(ringbuf == NULL || buf_size <0 ) return -1 ;
memset(ringbuf , 0 , sizeof(struct ring_buffer)) ;
ringbuf->buf = (unsigned char *)malloc(buf_size) ;
if(ringbuf->buf == NULL)
{
free(ringbuf->buf) ;
printf("can't not malloc the ring_buffer->buf!\n") ;
return -1;
}
else
{
printf("the ring_buffer->buf have been malloced!\n") ;
}
ringbuf->buf_size = buf_size ;
ringbuf->buf_head = 0 ;
ringbuf->buf_tail = 0 ;
}
5.3 写环形缓冲区
参考上面的图
int ring_buffer_write(struct ring_buffer * ringbuf,unsigned char * in_buf ,\
int write_len )
{
if(ringbuf == NULL || in_buf == NULL) return -1 ;
if(ringbuf->buf_head == ringbuf->buf_size)
{
printf("the ringbuf has been full\n");
return -1;
}
int free_space = ringbuf->buf_size - ringbuf->buf_data_len ;
int real_write_len = (write_len > free_space) ? free_space : write_len ;
int first_write_part = 0 ;
int second_write_part = 0 ;
if(ringbuf->buf_head >= ringbuf->buf_tail) /*如果头指针 > 尾指针 分为两部分来考虑*/
{
first_write_part = ringbuf->buf_size - ringbuf->buf_head ;
if(first_write_part > real_write_len)
{
first_write_part = real_write_len ;
}
memcpy(ringbuf->buf + ringbuf->buf_head , in_buf , first_write_part) ;
ringbuf->buf_head = (ringbuf->buf_head + first_write_part) % ringbuf->buf_size ;
ringbuf->buf_data_len += first_write_part ;
if(first_write_part < real_write_len)
{
second_write_part = real_write_len - first_write_part ;
memcpy(ringbuf->buf , in_buf + first_write_part , second_write_part) ;
ringbuf->buf_head = second_write_part ;
ringbuf->buf_data_len += second_write_part ;
}
}
else /* 如果尾指针> 头指针 直接写入*/
{
first_write_part = real_write_len ;
memcpy(ringbuf->buf + ringbuf->buf_head , in_buf , first_write_part) ;
ringbuf->buf_head += first_write_part ;
ringbuf->buf_data_len += first_write_part ;
}
return real_write_len ;
}
5.4 读环形缓冲区
int ring_buffer_read(struct ring_buffer * ringbuf,unsigned char * out_buf ,\
int len )
{
if(ringbuf == NULL|| out_buf ==NULL ) {
return -1 ;
}
if(ringbuf->buf_data_len == 0 )
{
return -1;
}
int real_Read_len = (len > ringbuf->buf_data_len ? ringbuf->buf_data_len : len );
int first_read_part = 0 ;
int retlen = 0 ;
if(ringbuf->buf_head > ringbuf->buf_tail)
{/*如果此时数据是连续的话*/
first_read_part = ringbuf->buf_head - ringbuf->buf_tail ;
first_read_part = (first_read_part > real_Read_len ? real_Read_len :first_read_part) ;
memcpy(out_buf , ringbuf->buf + ringbuf->buf_tail , first_read_part) ;
ringbuf->buf_tail += first_read_part ;
retlen = first_read_part ;
}
else
{
first_read_part = ringbuf->buf_size - ringbuf->buf_tail ;
first_read_part = (first_read_part > real_Read_len ? real_Read_len :first_read_part) ;
memcpy(out_buf , ringbuf->buf + ringbuf->buf_tail , first_read_part) ;
ringbuf->buf_tail = (ringbuf->buf_tail + first_read_part ) % ringbuf->buf_size ;
retlen = first_read_part ;
if(real_Read_len > first_read_part) {
int second_read_part = real_Read_len - first_read_part ;
memcpy(out_buf + first_read_part, ringbuf->buf , second_read_part) ;
ringbuf->buf_tail += second_read_part ;
retlen += second_read_part ;
}
}
return retlen ;
}
5.5 测试
- 首先先让环形缓冲区分配的 size=10写入1-10 看能否读取前8个数字出来
- 环形缓冲区分配的 size=10写入1-6 ,再读出2个, 此时头指针 》 尾指针 , 再次写入数7,8,9,10,11,12,13 一共七个数据 ,那么那会在两头进行写入数据,先写完编号7-10,再绕回到开头 写编号1 和 2 部分。
对应代码 :
if(ringbuf->buf_head >= ringbuf->buf_tail)
{/*从头指针到尾指针的部分*/
first_write_part = ringbuf->buf_size - ringbuf->buf_head ;
if(first_write_part > real_write_len)
{
first_write_part = real_write_len ;
}
memcpy(ringbuf->buf + ringbuf->buf_head , in_buf , first_write_part) ;
ringbuf->buf_head = (ringbuf->buf_head + first_write_part) % ringbuf->buf_size ;
ringbuf->buf_data_len += first_write_part ;
if(first_write_part < real_write_len)
{
second_write_part = real_write_len - first_write_part ;
memcpy(ringbuf->buf , in_buf + first_write_part , second_write_part) ;
ringbuf->buf_head = second_write_part ;
ringbuf->buf_data_len += second_write_part ;
}
}
3. 尾指针 》 头指针