当前位置: 首页 > news >正文

环形缓冲区

什么是环形缓冲区

环形缓冲区,也称为循环缓冲区或环形队列,是一种特殊的FIFO(先进先出)数据结构。它使用一块固定大小的内存空间来缓存数据,并通过两个指针(读指针和写指针)来管理数据的读写。当任意一个指针到达缓冲区末尾时,会自动回绕到缓冲区开头,形成一个"环"。

环形缓冲区的用途

  1. 串口通信
    在嵌入式设备中,串口是常用的通信接口。环形缓冲区可用于缓存收发数据,平衡通信速率差异。
  2. 音视频数据处理
    音视频数据往往是连续的数据流。使用环形缓冲区可以平滑数据的生成和消耗,避免数据丢失或延迟。
  3. 传感器数据采集
    传感器数据通常以固定频率采样。环形缓冲区可作为数据采集和处理之间的缓冲,降低实时性要求。
  4. 多线程数据传递
    在多线程编程中,环形缓冲区是一种简单高效的线程间通信方式,无需复杂的同步操作。
  5. 数据打包与解析
    一些通信协议使用特定的数据帧格式。环形缓冲区可用于数据的打包和解析,保证数据的完整性。

环形缓冲区的实现示例

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>// 环形缓冲区大小
#define BUFFER_SIZE 256// 定义解析器状态
typedef enum {STATE_WAIT_START,    // 等待消息开始STATE_READ_LENGTH,   // 读取消息长度STATE_READ_DATA      // 读取消息数据
} ParserState;typedef struct {uint8_t buffer[BUFFER_SIZE];volatile uint16_t head;volatile uint16_t tail;
} RingBuffer;void RingBuffer_Init(RingBuffer *rb) {rb->head = 0;rb->tail = 0;
}bool RingBuffer_Write(RingBuffer *rb, uint8_t data) {uint16_t next = (rb->head + 1) % BUFFER_SIZE;if (next == rb->tail) {// 缓冲区满return false;}rb->buffer[rb->head] = data;rb->head = next;return true;
}bool RingBuffer_Read(RingBuffer *rb, uint8_t *data) {if (rb->head == rb->tail) {// 判满return false;}*data = rb->buffer[rb->tail];rb->tail = (rb->tail + 1) % BUFFER_SIZE;return true;
}// 处理完整消息的函数
void process_message(const uint8_t *msg, uint8_t length) {for (uint8_t i = 0; i < length; i++) {printf("%c", msg[i]);}printf("\n");
}// 有限状态机解析器
void parse_messages(RingBuffer *rb) {static ParserState state = STATE_WAIT_START;static uint8_t msg_length = 0;static uint8_t msg_index = 0;static uint8_t message[128]; //uint8_t byte;while (RingBuffer_Read(rb, &byte)) {switch (state) {case STATE_WAIT_START:  // 等待消息起始if (byte == 0xAA) { // 0xAA是消息起始标志state = STATE_READ_LENGTH;}break;case STATE_READ_LENGTH:  // 读取消息长度msg_length = byte;if (msg_length > 0 && msg_length < sizeof(message)) {msg_index = 0;state = STATE_READ_DATA;} else {// 无效长度,重置状态state = STATE_WAIT_START;}break;case STATE_READ_DATA:  // 读取消息数据message[msg_index++] = byte;if (msg_index >= msg_length) {process_message(message, msg_length);state = STATE_WAIT_START;}break;default:state = STATE_WAIT_START;break;}}
}// 发送函数
void threadA_send(RingBuffer *rb, const uint8_t *msg, uint8_t length) {RingBuffer_Write(rb, 0xAA); // 起始标志RingBuffer_Write(rb, length); // 长度字段for (uint8_t i = 0; i < length; i++) {RingBuffer_Write(rb, msg[i]);}
}// 接收函数
void threadB_receive(RingBuffer *rb) {parse_messages(rb);
}void test_ring_buffer(void) {// 1. 初始化环形缓冲区RingBuffer rb;RingBuffer_Init(&rb);printf("=== 环形缓冲区测试开始 ===\n\n");// 2. 测试基本消息发送和接收printf("测试1: 基本消息收发\n");const uint8_t test_msg1[] = "Hello World";threadA_send(&rb, test_msg1, sizeof(test_msg1) - 1);threadB_receive(&rb);// 3. 测试空缓冲区printf("\n测试2: 空缓冲区读取\n");uint8_t temp;if (!RingBuffer_Read(&rb, &temp)) {printf("空缓冲区测试通过: 无法从空缓冲区读取数据\n");}// 4. 测试缓冲区满状态printf("\n测试3: 缓冲区满状态\n");uint8_t large_msg[BUFFER_SIZE];for (int i = 0; i < BUFFER_SIZE; i++) {large_msg[i] = 'A' + (i % 26);  // 填充A-Z循环}bool write_result = true;int write_count = 0;while (write_result && write_count < BUFFER_SIZE + 10) {write_result = RingBuffer_Write(&rb, large_msg[write_count % BUFFER_SIZE]);write_count++;}printf("写入计数: %d (应小于缓冲区大小)\n", write_count - 1);// 5. 测试长消息分段发送printf("\n测试4: 长消息分段发送\n");RingBuffer_Init(&rb);  // 重新初始化const uint8_t long_msg[] = "This is a long message to test multiple segments";const int SEGMENT_SIZE = 10;for (size_t i = 0; i < (size_t)(sizeof(long_msg) - 1); i += (size_t)SEGMENT_SIZE) {size_t current_length = ((sizeof(long_msg) - 1 - i) < (size_t)SEGMENT_SIZE) ? (sizeof(long_msg) - 1 - i) : (size_t)SEGMENT_SIZE;threadA_send(&rb, &long_msg[i], current_length);threadB_receive(&rb);}// 6. 测试无效消息printf("\n测试5: 无效消息处理\n");uint8_t invalid_msg[] = {0xAA, 0xFF, 0x01, 0x02};  // 无效长度for (size_t i = 0; i < sizeof(invalid_msg); i++) {RingBuffer_Write(&rb, invalid_msg[i]);}threadB_receive(&rb);// 7. 测试快速读写切换printf("\n测试6: 快速读写切换\n");const uint8_t test_msg2[] = "Test";for (int i = 0; i < 5; i++) {threadA_send(&rb, test_msg2, sizeof(test_msg2) - 1);threadB_receive(&rb);}printf("\n=== 环形缓冲区测试完成 ===\n");
}int main() {test_ring_buffer();return 0;
}

总结

与传统的数组或链表相比,环形缓冲区有以下优点:
1.无需频繁移动数据。环形缓冲区的读写指针移动不会导致数据搬移,效率更高。
2.自动处理缓冲区"满"和"空"的状态。通过读写指针的关系可以判断缓冲区状态,无需额外的计数器。
3. 适用于生产者-消费者模型。一个线程写入数据,另一个线程读取数据,天然支持异步处理。

相关文章:

环形缓冲区

什么是环形缓冲区 环形缓冲区,也称为循环缓冲区或环形队列,是一种特殊的FIFO(先进先出)数据结构。它使用一块固定大小的内存空间来缓存数据,并通过两个指针(读指针和写指针)来管理数据的读写。当任意一个指针到达缓冲区末尾时,会自动回绕到缓冲区开头,形成一个"环"。…...

jQuery-Word-Export 使用记录及完整修正文件下载 jquery.wordexport.js

参考资料&#xff1a; jQuery-Word-Export导出word_jquery.wordexport.js下载-CSDN博客 近期又需要自己做个 Html2Doc 的解决方案&#xff0c;因为客户又不想要 Html2pdf 的下载了&#xff0c;当初还给我费尽心思解决Html转pdf时中文输出的问题&#xff08;html转pdf文件下载之…...

云服务器部署WebSocket项目

WebSocket是一种在单个TCP连接上进行全双工通信的协议&#xff0c;其设计的目的是在Web浏览器和Web服务器之间进行实时通信&#xff08;实时Web&#xff09; WebSocket协议的优点包括&#xff1a; 1. 更高效的网络利用率&#xff1a;与HTTP相比&#xff0c;WebSocket的握手只…...

C#+数据库 实现动态权限设置

将权限信息存储在数据库中&#xff0c;支持动态调整。根据用户所属的角色、特定的功能模块&#xff0c;动态加载权限” 1. 数据库设计 根据这种需求&#xff0c;可以通过以下表设计&#xff1a; 用户表 (Users)&#xff1a;存储用户信息。角色表 (Roles)&#xff1a;存储角色…...

(原创)Android Studio新老界面UI切换及老版本下载地址

前言 这两天下载了一个新版的Android Studio&#xff0c;发现整个界面都发生了很大改动&#xff1a; 新的界面的一些设置可参考一些博客&#xff1a; Android Studio新版UI常用设置 但是对于一些急着开发的小伙伴来说&#xff0c;没有时间去适应&#xff0c;那么怎么办呢&am…...

Ubuntu24虚拟机-gnome-boxes

推荐使用gnome-boxes&#xff0c; virtualbox构建失败&#xff0c;multipass需要开启防火墙 sudo apt install gnome-boxes创建完毕&#xff5e;...

k8s rainbond centos7/win10 -20241124

参考 https://www.rainbond.com/ 国内一站式云原生平台 对centos7环境支持不太行 [lighthouseVM-16-5-centos ~]$ curl -o install.sh https://get.rainbond.com && bash ./install.sh 2024-11-24 09:56:57 ERROR: Ops! Docker daemon is not running. Start docke…...

SpringBoot+Vue滑雪社区网站设计与实现

【1】系统介绍 研究背景 随着互联网技术的快速发展和冰雪运动的普及&#xff0c;滑雪作为一种受欢迎的冬季运动项目&#xff0c;吸引了越来越多的爱好者。与此同时&#xff0c;社交媒体和在线社区平台的兴起为滑雪爱好者提供了一个交流经验、分享心得、获取信息的重要渠道。滑…...

MySql.2

sql查询语句执行过程 SQL 查询语句的执行过程是一个复杂的过程&#xff0c;涉及多个步骤。以下是典型的关系数据库管理系统 (RDBMS) 中 SQL 查询语句的执行过程概述&#xff1a; 1. ‌客户端发送查询‌ 用户通过 SQL 客户端或应用程序发送 SQL 查询语句给数据库服务器。 2. ‌…...

算法之区间和题目讲解

题干 难度&#xff1a;简单 题目分析 题目要求算出每个指定区间内元素的总和。 然而&#xff0c;区间在输入的最下面&#xff0c;所以按照暴力破解的思路&#xff0c;我们首先要遍历数组&#xff0c;把它的值都存进去。 然后&#xff0c;遍历下面的区间&#xff0c;从索引a…...

价格分类(神经网络)

# 1.导入依赖包 import timeimport torch import torch.nn as nn import torch.optim as optimfrom torch.utils.data import TensorDataset, DataLoader from sklearn.model_selection import train_test_splitimport numpy as np import pandas as pd import matplotlib.pypl…...

对智能电视直播App的恶意监控

首先我们要指出中国广电总局推出的一个政策性文件是恶意监控的始作俑者&#xff0c;这个广电总局的政策性文件禁止智能电视和电视盒子安装直播软件。应该说这个政策性文件是为了保护特殊利益集团&#xff0c;阻挠技术进步和发展的。 有那么一些电视机和电视盒子的厂商和电信运…...

【JavaEE初阶】多线程初阶下部

文章目录 前言一、volatile关键字volatile 能保证内存可见性 二、wait 和 notify2.1 wait()方法2.2 notify()方法2.3 notifyAll()方法2.4 wait 和 sleep 的对比&#xff08;面试题&#xff09; 三、多线程案例单例模式 四、总结-保证线程安全的思路五、对比线程和进程总结 前言…...

macOS上进行Ant Design Pro实战教程(一)

由于一个AI项目的前端使用了umi&#xff0c;本教程根据阿里官网上的 《Ant Design 实战教程&#xff08;beta 版&#xff09;》来实操一下&#xff0c;我使用macOS操作系统&#xff0c;VS Code 开发环境。 一、开发环境 1、安装nodejs, npm, yarn 官网上建议使用cnpm&#xf…...

智能合约运行原理

点个关注吧&#xff01;&#xff01; 用一句话来总结&#xff0c;智能合约就像是一个自动售货机&#xff1a;你投入硬币&#xff08;触发条件&#xff09;&#xff0c;选择商品&#xff08;执行合约&#xff09;&#xff0c;然后机器就会自动给你商品&#xff08;执行结果&…...

安卓动态添加View

在安卓应用中&#xff0c;有很多时候需要动态添加View。比如从后台获取商品列表&#xff0c;根据商品数量在页面渲染对应数量的条目&#xff0c;这时候就需要动态添加View。 1.动态添加View的方法 动态添加View有两种方法&#xff1a; 由代码生成子View&#xff1a;这种方式…...

前端预览pdf文件流

需求 后端接口返回pdf文件流&#xff0c;实现新窗口预览pdf。 解决方案 把后端返回的pdf文件流转为blob路径&#xff0c;利用浏览器直接预览。 具体实现步骤 1、引入axios import axios from axios;2、创建预览方法&#xff08;具体使用时将axios的请求路径替换为你的后端…...

【测试工具JMeter篇】JMeter性能测试入门级教程(一)出炉,测试君请各位收藏了!!!

一、前言 Apache JMeter是纯Java的开源软件&#xff0c;最初由Apache软件基金会的Stefano Mazzocchi开发&#xff0c;旨在加载测试功能行为和测量性能。可以使用JMeter进行性能测试&#xff0c;即针对重负载、多用户和并发流量测试Web应用程序。 我们选择JMeter原因 是否测试过…...

【zookeeper03】消息队列与微服务之zookeeper集群部署

ZooKeeper 集群部署 1.ZooKeeper 集群介绍 ZooKeeper集群用于解决单点和单机性能及数据高可用等问题。 集群结构 Zookeeper集群基于Master/Slave的模型 处于主要地位负责处理写操作)的主机称为Leader节点&#xff0c;处于次要地位主要负责处理读操作的主机称为 follower 节点…...

从 Llama 1 到 3.1:Llama 模型架构演进详解

编者按&#xff1a; 面对 Llama 模型家族的持续更新&#xff0c;您是否想要了解它们之间的关键区别和实际性能表现&#xff1f;本文将探讨 Llama 系列模型的架构演变&#xff0c;梳理了 Llama 模型从 1.0 到 3.1 的完整演进历程&#xff0c;深入剖析了每个版本的技术创新&#…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...