环形缓冲区的使用

环形缓冲区(Circular Buffer),又被称为环形队列或循环缓冲区,是一种常用的数据结构,特别适用于缓冲流数据的场景,如硬件设备的数据交换、网络通信等

1. 什么是环形缓冲区

环形缓冲区是一个先进先出(FIFO)的闭环的存储空间。相当于就是一个固定大小的数组,和两个指针,读指针和写指针。环形 缓冲区的”环形“特性意味着当任一指针到达数组的末端的时候,会自动回绕到数据开始的位置。

2. 环形缓冲区读写指针

  • 写指针(head): 指向环形缓冲区下一个数据将要写入的位置 , 当有新的数据写入缓冲区的时候,数据会被存储在写指针指向的位置,然后写指针向后移动。如果移动到数组的末尾,并且还有数据要写入的时候,回绕回到数组的开头,继续写入数据。
  • 读指针(tail) :指向下一个将要被读取的数组的位置,和写指针类似,如果读指针度到了数组的末尾,还有数据要读取,它回绕回到数组的开头,继续读取数据。

3. 环形缓冲区读写区域

  • 当写指针(head)在读指针(tail)前面的时候,表示数据是连续的。可以直接从读指针位置开始连续读取数据,如图
    在这里插入图片描述
  • 当读指针(tail) 在写指针前面的时候(head),表示数据跨越了缓冲区的末尾回到开头
    在这里插入图片描述

4. 当数据读写超出区域时

4.1 数据写入超出可写区域时

  1. 等待: 如果没有足够的空间写入新数据,可以等待读取更多的数据,释放空间
  2. 覆盖旧数据: 如果最新的数据比旧数据更重要,可以选择覆盖旧数据。这通常在数据流应用中使用,比如实时视频流处理。
  3. 拒绝写入: 直接拒绝写入操作,并可能返回一个错误,告诉调用者缓冲区已满。
  4. 动态扩容: 如果实现允许,可以动态增加缓冲区的大小以容纳更多数据。这可能需要重新分配内存和移动现有数据,从而增加复杂性和性能开销

假设一个大小为10的环形缓冲区,当前写指针在位置7,读指针在位置2,可写区域是3、4、5、6(4个位置),但你想写入5个字节的数据。
如果选择覆盖,那么从位置3开始的5个字节会写入,这将导致位置2的数据被覆盖(在实际应用中需要小心处理读写指针)。

4.2 数据读取超出可读区域

  1. 部分读取: 只读取可读区域内的数据,并返回实际读取的长度。
  2. 等待: 如果缓冲区中的数据不足,可以等待直到有足够的数据被写入。
  3. 返回错误: 如果必须一次性读取足够的数据,不足时返回错误。

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 测试

  1. 首先先让环形缓冲区分配的 size=10写入1-10 看能否读取前8个数字出来
    在这里插入图片描述
  2. 环形缓冲区分配的 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. 尾指针 》 头指针
在这里插入图片描述

  • 16
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值