C语言环形缓冲区:原理、实现与图解详解 环形缓冲区实现

news/2025/1/12 1:19:34 标签: 网络, 环形缓冲区, 数据结构, C, c语言

        ChongQu.html" title=环形缓冲区>环形缓冲区(Circular Buffer),也称为循环队列或Ring Buffer,是一种非常实用的数据结构,尤其在生产者-消费者模型中,用于解决数据传输速度不匹配的问题。本文将深入讲解C语言中ChongQu.html" title=环形缓冲区>环形缓冲区的实现原理、关键概念以及使用方法,并结合图解进行形象的解释。

1. 什么是ChongQu.html" title=环形缓冲区>环形缓冲区?

        想象一个首尾相连的管道,数据从一端流入,从另一端流出。这就是ChongQu.html" title=环形缓冲区>环形缓冲区的基本模型。它使用一段固定大小的内存空间,通过读写指针的循环移动,实现高效的数据传递,避免了频繁的数据复制和移动。

2. 核心概念

        ChongQu.html" title=环形缓冲区>环形缓冲区就像一个首尾相连的圆环,数据在这个环上循环流动。我们使用数组来模拟这个环,并用两个指针 start(读指针)和 end(写指针)来跟踪数据的读写位置

  • 缓冲区大小(Length/Capacity): 缓冲区可以存储的最大数据量。
  • 读指针(Start/Head): 指向下一个要读取的数据的位置。
  • 写指针(End/Tail): 指向下一个要写入数据的位置。
  • 空闲位置: 为了区分缓冲区“满”和“空”的状态,通常会保留一个空闲位置。这意味着缓冲区实际可用的存储空间比其物理大小少 1。这是非常重要的设计,避免了 startend 指针重合时,无法区分满和空的状态。

3. 图解ChongQu.html" title=环形缓冲区>环形缓冲区

        假设我们有一个大小为 5 的数组来模拟ChongQu.html" title=环形缓冲区>环形缓冲区(实际有效存储空间为 4,需要保留一个空位)。

+---+---+---+---+---+
|   |   |   |   |   |
+---+---+---+---+---+
  0   1   2   3   4 (数组索引)
  ^           ^
  |           |
start       end

初始状态(空): startend 指向同一个位置(通常是 0)。表示缓冲区没有任何数据。 

+---+---+---+---+---+
|   |   |   |   |   |
+---+---+---+---+---+
^                   ^
|                   |
start             end

首位相连,这其实end和start是重合的

写入数据: end 指针向后移动,并将数据写入到 end 指向的位置。

写入 'A'、'B'、'C':

+---+---+---+---+---+
| A | B | C |   |   |
+---+---+---+---+---+
^               ^
|               |
start           end

读取数据: start 指针向后移动,并读取 start 指向位置的数据。

读取 'A':

+---+---+---+---+---+
|   | B | C |   |   |
+---+---+---+---+---+
    ^           ^
    |           |
  start         end

环绕:end 指针到达数组末尾时,它会“绕回”到数组的开头。

写入 'D' 和 'E':

+---+---+---+---+---+
| E | B | C | D |   |
+---+---+---+---+---+
    ^       ^
    |       |
  start     end

缓冲区满:end 指针的下一个位置(环绕后)与 start 指针重合时,缓冲区就满了。此时不能再写入数据,除非先读取数据腾出空间。 

        如果继续写入,end 会追上 start,导致数据覆盖。为了避免这种情况,我们保留一个空位,即当 (end + 1) % length == start 时,就认为缓冲区已满。

 4. ChongQu.html" title=环形缓冲区>环形缓冲区基本功能代码说明

 4.1 头文件函数、宏定义总览:

#ifndef _ring_buffer_h
#define _ring_buffer_h



typedef struct {
    char *buffer; // 
    int length; // 缓冲区空间大小
    int start; // 读指针位
    int end; // 写指针位
} RingBuffer;


RingBuffer *RingBuffer_create(int length);

void RingBuffer_destroy(RingBuffer *buffer);

int RingBuffer_read(RingBuffer *buffer, char *target, int amount);

int RingBuffer_write(RingBuffer *buffer, char *data, int length);

void *RingBuffer_gets(RingBuffer *buffer);

#define RingBuffer_available_data(B) (((B)->end - (B)->start + (B)->length) % (B)->length) 

#define RingBuffer_available_space(B) ((B)->length - RingBuffer_available_data(B) - 1)

#define RingBuffer_full(B) (RingBuffer_available_data((B)) + 1 == (B)->length)

#define RingBuffer_empty(B) (RingBuffer_available_data((B)) == 0)

#define RingBuffer_puts(B, D) RingBuffer_write((B), bdata((D)), blength((D)))

#define RingBuffer_get_all(B) RingBuffer_gets((B), RingBuffer_available_data((B)))

#define RingBuffer_starts_at(B) ((B)->buffer + (B)->start)

#define RingBuffer_ends_at(B) ((B)->buffer + (B)->end)

#define RingBuffer_commit_read(B, A) ((B)->start = ((B)->start + (A)) % (B)->length) //将读指针向前移动A个位置。

#define RingBuffer_commit_write(B, A) ((B)->end = ((B)->end + (A)) % (B)->length) // 将写指针向前移动A个位置。 

#endif

 4.1.1 RingBuffer结构体

typedef struct {
    char *buffer; // 存储数据的缓冲区
    int length;   // 缓冲区总长度(包含一个空闲位置)
    int start;    // 读指针
    int end;      // 写指针
} RingBuffer;
  • buffer: 指向实际存储数据的内存区域。
  • length: 缓冲区的大小。为了区分空和满的状态,实际可用的存储空间是 length - 1
  • start: 读指针,指向下一个要读取的数据的位置。
  • end: 写指针,指向下一个要写入数据的位置。

4.1.2  宏定义:

这些宏定义简化了对ChongQu.html" title=环形缓冲区>环形缓冲区的操作和状态判断。

  • RingBuffer_available_data(B): 计算缓冲区中已有的数据量。使用模运算 % 实现环形效果。
  • RingBuffer_available_space(B): 计算缓冲区中可用的空间量。注意要减去 1,因为要保留一个空位。
  • RingBuffer_full(B): 判断缓冲区是否已满。
  • RingBuffer_empty(B): 判断缓冲区是否为空。
  • RingBuffer_starts_at(B): 获取 start 指针指向的地址。
  • RingBuffer_ends_at(B): 获取 end 指针指向的地址。
  • RingBuffer_commit_read(B, A): 将 start 指针向前移动 A 个位置。
  • RingBuffer_commit_write(B, A): 将 end 指针向前移动 A 个位置。

4.2 .c文件中的函数实现 

RingBuffer_create(int length) 创建ChongQu.html" title=环形缓冲区>环形缓冲区。

RingBuffer *RingBuffer_create(int length) {
    RingBuffer *buffer = calloc(1, sizeof(RingBuffer)); // 分配 RingBuffer 结构体内存
    check_mem(buffer); // 检查内存分配是否成功
    buffer->length = length + 1; // 缓冲区实际长度比length大1
    buffer->start = 0;
    buffer->end = 0;
    buffer->buffer = calloc(buffer->length, 1); // 分配缓冲区内存
    check_mem(buffer->buffer); // 检查内存分配是否成功
    return buffer;
error:
    if (buffer) { // 内存分配失败时释放已分配的内存,防止内存泄漏
        free(buffer);
    }
    return NULL;
}

RingBuffer_destroy(RingBuffer *buffer) 销毁ChongQu.html" title=环形缓冲区>环形缓冲区,释放内存。 

void RingBuffer_destroy(RingBuffer *buffer) {
    if (buffer) {
        free(buffer->buffer); // 释放缓冲区内存
        free(buffer);        // 释放 RingBuffer 结构体内存
    }
}

RingBuffer_write(RingBuffer *buffer, char *data, int length) 向缓冲区写入数据。 

int RingBuffer_write(RingBuffer *buffer, char *data, int length) {
    check(length > 0 && buffer != NULL && data != NULL, "Invalid inputs."); // 参数检查
    check(length <= RingBuffer_available_space(buffer), // 检查是否有足够的空间
          "Not enough space: %d request, %d available",
          RingBuffer_available_data(buffer), length);
    for (int i = 0; i < length; i++) {
        *(RingBuffer_ends_at(buffer)) = *(data + i); // 写入数据
        RingBuffer_commit_write(buffer, 1);         // 移动 end 指针
    }
    return length; // 返回实际写入的字节数
error:
    return -1; // 写入失败
}

RingBuffer_read(RingBuffer *buffer, char *target, int amount) 从缓冲区读取数据。 

int RingBuffer_read(RingBuffer *buffer, char *target, int amount) {
    check(amount > 0 && buffer != NULL && target != NULL, "Invalid inputs."); // 参数检查
    check_debug(amount <= RingBuffer_available_data(buffer), // 检查是否有足够的数据
                "Not enough in the buffer: has %d, needs %d",
                RingBuffer_available_data(buffer), amount);
    for (int i = 0; i < amount; i++) {
        *(target + i) = *(RingBuffer_starts_at(buffer)); // 读取数据
        RingBuffer_commit_read(buffer, 1);             // 移动 start 指针
    }
    return amount; // 返回实际读取的字节数
error:
    return -1; // 读取失败
}

RingBuffer_gets(RingBuffer *buffer) 获取缓冲区中的所有数据,但不修改缓冲区状态(即不移动 start 指针)。 

void *RingBuffer_gets(RingBuffer *buffer) {
    check(buffer != NULL, "Invalid inputs.");

    char *result = NULL;

    if (RingBuffer_empty(buffer)) { // 缓冲区为空
        result = calloc(1, 1);
        check_mem(result);
        *result = '\0';
        return result;
    } else { // 缓冲区非空
        int n = RingBuffer_available_data(buffer);
        result = calloc(1, n + 1); // 分配 n+1 个字节,用于存储字符串和空终止符
        check_mem(result);

        int i = 0;
        int current = buffer->start; // 保存 start 指针

        while (i < n) {
            result[i] = buffer->buffer[current];
            current = (current + 1) % buffer->length; // 循环移动 current
            i++;
        }
        result[n] = '\0'; // 添加字符串结束符
    }

    return result;
error:
    if (result) free(result);
    return NULL;
}

 5. ChongQu.html" title=环形缓冲区>环形缓冲区的优势及其应用领域

应用场景:

  • 生产者-消费者模型: 这是ChongQu.html" title=环形缓冲区>环形缓冲区最经典的应用场景。生产者向缓冲区写入数据,消费者从缓冲区读取数据,缓冲区起到了缓冲和解耦的作用。
  • 网络数据传输:网络编程中,ChongQu.html" title=环形缓冲区>环形缓冲区常用于接收和发送数据,解决网络速度和应用程序处理速度不匹配的问题。
  • 音频/视频处理: 音频和视频数据通常以流的形式传输,ChongQu.html" title=环形缓冲区>环形缓冲区可以有效地管理这些数据流。
  • 中断处理: 在嵌入式系统中,中断处理程序通常会将数据写入ChongQu.html" title=环形缓冲区>环形缓冲区,然后由主程序进行处理。

相比于其他数据结构的优势:

  • 高效的入队和出队操作: 只需要移动指针,不需要像普通队列那样移动大量数据。
  • 避免频繁的内存分配和释放: 使用固定大小的内存空间,提高了性能。
  • 适用于流式数据处理: 能够很好地处理连续不断的数据流。

http://www.niftyadmin.cn/n/5820462.html

相关文章

基于深度学习的视觉检测小项目(十) 通过样式表改变界面的外观

一、创建色卡模板文件 在PS中打开之前创建的色卡文件&#xff0c;用吸管拾色器吸取各个色卡的色彩值&#xff1a; 并保存为JSON文件&#xff0c;color_card.json&#xff0c;文件保存在项目的/settings目录下&#xff1a; {"colors": {"RED": "#dc1…

思维转换:突破思维桎梏,创造更高效的工作与生活

在现代职场和生活中&#xff0c;我们经常面临着各种挑战和问题&#xff0c;有时候虽然付出了很多努力&#xff0c;但依然难以找到更有效的解决方案。这时&#xff0c;或许我们需要的不是更多的努力&#xff0c;而是一次“思维转换”。这一概念看似简单&#xff0c;但它背后却蕴…

HBuilderX打包ios保姆式教程

1、登录苹果开发者后台并登录已认证开发者账号ID Sign In - Apple 2、创建标识符&#xff08;App ID&#xff09;、证书&#xff0c;描述文件 3、首先创建标识符&#xff0c;用于新建App应用 3-1、App的话直接选择第一个App IDs&#xff0c;点击右上角继续 3-2、选择App&#x…

25.1.10学习笔记(算法(滑动窗口))

题目&#xff1a;在leetcode上 解释&#xff1a;什么是滑动窗口呢&#xff0c;在这道题里面&#xff0c;子串的长度为k,所以我们就可以将这个子串看为一个窗口&#xff0c;每次去统计窗口里面有多少满足要求&#xff0c;然后进行相关值的加减&#xff0c;滑动就体现在子串的第…

[OPEN SQL] 限定选择行数

本次操作使用的数据库表为SCUSTOM&#xff0c;其字段内容如下所示 航班用户(SCUSTOM) 该数据库表中的部分值如下所示 指定查询多少行数据&#xff0c;我们可以使用语法UP TO n ROWS来实现对数据前n项的查询 语法格式 SELECT * FROM <dbtab> UP TO n ROWS 参数说明 db…

Js的回调函数

一、什么是回调函数&#xff08;Callback&#xff09;&#xff1f; 回调函数&#xff08;Callback Function&#xff09;是指一个函数被作为参数传递给另一个函数&#xff0c;并在特定事件发生或操作完成时执行。 可以通俗地理解为一种“委托”机制。 在JavaScript中&#xff0…

2025-1-9 QT 使用 QXlsx库 读取 .xlsx 文件 —— 导入 QXlsx库以及读取 .xlsx 的源码 实践出真知,你我共勉

文章目录 1. 导入QXlsx库2. 使用 QXlsx库 读取 .xlsx 文件小结 网上有很多教程&#xff0c;但太费劲了&#xff0c;这里有个非常简便的好方法&#xff0c;分享给大家。 1. 导入QXlsx库 转载链接 &#xff1a;https://github.com/QtExcel/QXlsx/blob/master/HowToSetProject.md…

【源码+文档+调试讲解】数学辅导微信小程序

摘 要 网络的广泛应用给生活带来了十分的便利。所以把数学辅导管理与现在网络相结合&#xff0c;利用java技术建设数学辅导微信小程序&#xff0c;实现数学辅导的信息化。则对于进一步提高数学辅导管理发展&#xff0c;丰富数学辅导管理经验能起到不少的促进作用。 数学辅导微…