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

关于循环缓冲区

循环缓冲区

在这里插入图片描述

说明

XXX123456XXX
bufferreadPointer->writePointer->end
789XXX123456
bufferwritePointer->readPointer->end

后面写不下,回到开头去写。

注意读指针、写指针都只能“向后走”,走到最后就回到开头。

读写应当通过读指针、写指针去做,不然就乱套了。

注意

不允许将整个数组全部使用,否则无法区分当前是没有数据,还是数据已满!

没有数据

XXXXXXXXXXXX
writePointer->
readPointer->

数据充满整个数组

111212345678910
writepointer->
readPointer

当然,这也可以通过加入一个Flag变量去标记,目前到底有没有数据。如果这样的话,每次读写后都应当检查writePointer是否等于readPointer,若相等则根据情况更新Flag变量。

相关代码

定义
#define CIRCULAR_BUFFER_SIZE 12 //缓冲区大小,根据需要修改typedef struct
{uint8_t buffer[CIRCULAR_BUFFER_SIZE];uint8_t* writePointer;uint8_t* readPointer;uint8_t* end;//缓冲区真正的结尾
}CircularBuffer;
初始化
/*** @brief 初始化一个循环缓冲区* @param cb 缓冲区指针* @note 可以使用此函数清空循环缓冲区*/
void circularBufferInit(CircularBuffer* cb);/*** @brief 初始化一个循环缓冲区* @param cb 缓冲区指针* @note 可以使用此函数清空循环缓冲区*/
void circularBufferInit(CircularBuffer* cvoid circularBufferInit(CircularBuffer* cb)
{//memset(cb, 0x00, CIRCULAR_BUFFER_SIZE);//其实不赋值也无所谓cb->writePointer = cb->buffer;cb->readPointer = cb->buffer;cb->end = cb->buffer + CIRCULAR_BUFFER_SIZE - 1;//缓冲区真正的结尾
};
数据是否存在
/*** @brief 看看循环缓冲区中是否存在数据* @param cb 循环缓冲区指针* @return true 有* @return false 没有*/
bool circularBufferDataExists(CircularBuffer* cb);bool circularBufferDataExists(CircularBuffer* cb)
{return cb->writePointer != cb->readPointer;
}
数据长度

当前存储的数据长度,或者说允许读取的长度

请结合前面的表格理解代码

/*** @brief 得到允许读取的数据长度* @param cb 循环缓冲区指针* @return 允许读取的数据长度* @note 也就是未处理的数据长度*/
size_t circularBufferReadAllow(CircularBuffer* cb);size_t circularBufferReadAllow(CircularBuffer* cb)
{//if (cb->writePointer == cb->readPointer)//{//    cb->readAllow = 0;//二者相等时没有数据//}//else if (cb->writePointer > cb->readPointer)//{//    cb->readAllow = cb->writePointer - cb->readPointer;//}//else//{//    cb->readAllow = CIRCULAR_BUFFER_SIZE - (cb->readPointer - cb->writePointer);//}//与上面的代码等价size_t readAllow = (cb->writePointer + CIRCULAR_BUFFER_SIZE - cb->readPointer) % CIRCULAR_BUFFER_SIZE;return readAllow;
}
剩余空间

剩余空间,或者说允许写入的长度,注意不允许全部写满

请结合前面的表格理解代码

/*** @brief 得到允许写入的数据长度* @param cb 循环缓冲区指针* @return 允许写入的数据长度*/
size_t circularBufferWriteAllow(CircularBuffer* cb);size_t circularBufferWriteAllow(CircularBuffer* cb)
{//size_t writeAllow = 0;//if (cb->writePointer == cb->readPointer)//{//    writeAllow = CIRCULAR_BUFFER_SIZE - 1;//不允许整个数组都存入数据//}        //else if (cb->writePointer > cb->readPointer)//{//    writeAllow = CIRCULAR_BUFFER_SIZE - (cb->writePointer - cb->readPointer) - 1;//}//else//{//    writeAllow = cb->readPointer - cb->writePointer - 1;//}//与上面的代码等价//不允许整个数组都存入数据size_t writeAllow = CIRCULAR_BUFFER_SIZE - (cb->writePointer + CIRCULAR_BUFFER_SIZE - cb->readPointer) % CIRCULAR_BUFFER_SIZE - 1;return writeAllow;
}

写入数据

注意,如果end处有写入,写指针就需要回到开头

写指针不需要返回开头的情况

以写入4个为例

原本

XX123XXXXXXX
bufferreadPointerwritePointerend

之后

XX1234567XXX
bufferreadPointerwritePointerend

写指针需要返回开头的情况

写不下需要返回开头!

以写四个为例

原本

XXXXXXX123XX
bufferreadPointerwritePointerend

之后

67XXXXX12345
bufferwritePointerreadPointerend

实现写入的代码

/*** @brief 向缓冲区写入数据* @param cb 循环缓冲区指针* @param data 要写入的数据指针* @param length 要写入的数据的长度* @return true 成功* @return false 失败*/
bool circularBufferWrite(CircularBuffer* cb, uint8_t* data, size_t length);bool circularBufferWrite(CircularBuffer* cb, uint8_t* data, size_t length)
{if (length == 0)return true;if (length > circularBufferWriteAllow(cb))return false;if (cb->writePointer + length <= cb->end)//不需要返回开头的情况{memcpy(cb->writePointer, data, length);cb->writePointer += length;}else//需要返回开头的情况{size_t endLength = cb->end - cb->writePointer + 1;//结尾部分需要拷贝的数量size_t beginLength = length - endLength;//开头部分需要拷贝的数量memcpy(cb->writePointer, data, endLength);//拷贝到后面        memcpy(cb->buffer, data + endLength, beginLength);//拷贝到前面cb->writePointer = cb->buffer + beginLength;}return true;
}

读取数据

以读取5个数据为例

读指针不需要返回开头的情况

读取前

XX1234567XXX
bufferreadPointerwritePointerend

读取后

XXXXXXX67XXX
bufferreadPointerwritePointerend

读指针需要返回开头的情况

读取前

34567XXXXX12
bufferwritePointerreadPointerend

读取后

XXX67XXXXXXX
bufferreadPointerwritePointerend

实现读取的代码

/*** @brief 从循环缓冲区中读出数据* @param cb 循环缓冲区指针* @param buffer 读取到的缓冲区* @param length 要求读取的长度* @return true 成功* @return false 失败*/
bool circularBufferRead(CircularBuffer* cb, uint8_t* buffer, size_t length);bool circularBufferRead(CircularBuffer* cb, uint8_t* buffer, size_t length)
{if (length == 0)return true;if (length > circularBufferReadAllow(cb))return false;if (cb->readPointer + length <= cb->end)//不需要返回开头的情况{memcpy(buffer, cb->readPointer, length);cb->readPointer += length;}else//需要返回开头的情况{size_t endLength = cb->end - cb->readPointer + 1;//结尾部分需要拷贝的数量size_t beginLength = length - endLength;//开头部分需要拷贝的数量memcpy(buffer, cb->readPointer, endLength);//拷贝后面的memcpy(buffer + endLength, cb->buffer, beginLength);//拷贝前面的cb->readPointer = cb->buffer + beginLength;}return true;
}

查找数据

通常是要查找通信协议的数据头

注意,不返回目标的位置,因为读取的操作应当通过读指针实现。

找到后,将读指针指向目标。

结束循环的条件是迭代器指向写指针

以查找1 2 3 4为例

迭代器不需要返回开头的情况

找到前

XXX??1234?XX
bufferreadPointerwritePointerend
1234
it->

找到后

XXXXX1234?XX
bufferreadPointerwritePointerend
1234
it

迭代器需要返回开头的情况

找到前

34?XXXX???12
bufferwritePointerreadPointerend
1234
it->

临近尾部要把目标数据拆分成两部分分别比较

34?XXXXX??12
bufferwritePointerreadPointerend
4123
it->

找到后

34?XXXXXXX12
bufferwritePointerreadPointerend
3412
it

实现查找的代码

/*** @brief 在循环缓冲区中查找特定数据,如果成功移动读指针到目标位置* @param cb 循环缓冲区指针* @param target 目标数据指针* @param length 目标数据长度* @return true 找到了* @return false 没有找到* @note 从读指针的位置开始找,如果找到,会移动读指针,也就是抛弃路径上的数据*/
bool circularBufferFindAndMove(CircularBuffer* cb, uint8_t* target, size_t length);bool circularBufferFindAndMove(CircularBuffer* cb, uint8_t* target, size_t length)
{if (target == NULL) return false;if (length == 0) return true;if (length > circularBufferReadAllow(cb)) return false;//目标长度超过当前数据长度,错误uint8_t* it = cb->readPointer;//遍历时的迭代器while (it != cb->writePointer){//目前不涉及迭代器返回开头的情况if (it + length <= cb->end){if (memcmp(it, target, length) == 0){cb->readPointer = it;return true;}}else{size_t endLength = cb->end - it + 1;//结尾部分需要比较的数量size_t beginLength = length - endLength;//开头部分需要比较的数量if (memcmp(it, target, endLength) == 0 && memcmp(cb->buffer, target + endLength, beginLength) == 0){cb->readPointer = it;return true;}}it++;if (it > cb->end) it = cb->buffer;//回到开头}return false;
}

完整代码

我这里以C实现它

CircularBuffer.h

#pragma once#ifdef __cplusplus
extern "C" {
#endif#include <stdint.h>
#include <string.h>
#include <stdbool.h>#define CIRCULAR_BUFFER_SIZE 12 //缓冲区大小,根据需要修改typedef struct
{uint8_t buffer[CIRCULAR_BUFFER_SIZE];uint8_t* writePointer;uint8_t* readPointer;uint8_t* end;//缓冲区真正的结尾
}CircularBuffer;/*** @brief 初始化一个循环缓冲区* @param cb 缓冲区指针* @note 可以使用此函数清空循环缓冲区*/
void circularBufferInit(CircularBuffer* cb);/*** @brief 得到允许读取的数据长度* @param cb 循环缓冲区指针* @return 允许读取的数据长度* @note 也就是未处理的数据长度*/
size_t circularBufferReadAllow(CircularBuffer* cb);/*** @brief 得到允许写入的数据长度* @param cb 循环缓冲区指针* @return 允许写入的数据长度*/
size_t circularBufferWriteAllow(CircularBuffer* cb);/*** @brief 向缓冲区写入数据* @param cb 循环缓冲区指针* @param data 要写入的数据指针* @param length 要写入的数据的长度* @return true 成功* @return false 失败*/
bool circularBufferWrite(CircularBuffer* cb, uint8_t* data, size_t length);/*** @brief 看看循环缓冲区中是否存在数据* @param cb 循环缓冲区指针* @return true 有* @return false 没有*/
bool circularBufferDataExists(CircularBuffer* cb);/*** @brief 从循环缓冲区中读出数据* @param cb 循环缓冲区指针* @param buffer 读取到的缓冲区* @param length 要求读取的长度* @return true 成功* @return false 失败*/
bool circularBufferRead(CircularBuffer* cb, uint8_t* buffer, size_t length);/*** @brief 在指定的循环缓冲区中删除数据* @param cb 循环缓冲区指针* @param length 要删除的长度* @return true 成功* @return false 失败* @note 与读取相似,移动读指针即可*/
bool circularBufferDelete(CircularBuffer* cb, size_t length);/*** @brief 在循环缓冲区中查找特定数据,如果成功移动读指针到目标位置* @param cb 循环缓冲区指针* @param target 目标数据指针* @param length 目标数据长度* @return true 找到了* @return false 没有找到* @note 从读指针的位置开始找,如果找到,会移动读指针,也就是抛弃路径上的数据*/
bool circularBufferFindAndMove(CircularBuffer* cb, uint8_t* target, size_t length);#ifdef __cplusplus
}
#endif

CircularBuffer.c

#include "CircularBuffer.h"void circularBufferInit(CircularBuffer* cb)
{//memset(cb, 0x00, CIRCULAR_BUFFER_SIZE);//其实不赋值也无所谓cb->writePointer = cb->buffer;cb->readPointer = cb->buffer;cb->end = cb->buffer + CIRCULAR_BUFFER_SIZE - 1;//缓冲区真正的结尾
}size_t circularBufferReadAllow(CircularBuffer* cb)
{//if (cb->writePointer == cb->readPointer)//{//    cb->readAllow = 0;//二者相等时没有数据//}//else if (cb->writePointer > cb->readPointer)//{//    cb->readAllow = cb->writePointer - cb->readPointer;//}//else//{//    cb->readAllow = CIRCULAR_BUFFER_SIZE - (cb->readPointer - cb->writePointer);//}//与上面的代码等价size_t readAllow = (cb->writePointer + CIRCULAR_BUFFER_SIZE - cb->readPointer) % CIRCULAR_BUFFER_SIZE;return readAllow;
}size_t circularBufferWriteAllow(CircularBuffer* cb)
{//size_t writeAllow = 0;//if (cb->writePointer == cb->readPointer)//{//    writeAllow = CIRCULAR_BUFFER_SIZE - 1;//不允许整个数组都存入数据//}        //else if (cb->writePointer > cb->readPointer)//{//    writeAllow = CIRCULAR_BUFFER_SIZE - (cb->writePointer - cb->readPointer) - 1;//}//else//{//    writeAllow = cb->readPointer - cb->writePointer - 1;//}//与上面的代码等价//不允许整个数组都存入数据size_t writeAllow = CIRCULAR_BUFFER_SIZE - (cb->writePointer + CIRCULAR_BUFFER_SIZE - cb->readPointer) % CIRCULAR_BUFFER_SIZE - 1;return writeAllow;
}bool circularBufferWrite(CircularBuffer* cb, uint8_t* data, size_t length)
{if (length == 0)return true;if (length > circularBufferWriteAllow(cb))return false;if (cb->writePointer + length <= cb->end)//不需要返回开头的情况{memcpy(cb->writePointer, data, length);cb->writePointer += length;}else//需要返回开头的情况{size_t endLength = cb->end - cb->writePointer + 1;//结尾部分需要拷贝的数量size_t beginLength = length - endLength;//开头部分需要拷贝的数量memcpy(cb->writePointer, data, endLength);//拷贝到后面        memcpy(cb->buffer, data + endLength, beginLength);//拷贝到前面cb->writePointer = cb->buffer + beginLength;}return true;
}bool circularBufferDataExists(CircularBuffer* cb)
{return cb->writePointer != cb->readPointer;
}bool circularBufferRead(CircularBuffer* cb, uint8_t* buffer, size_t length)
{if (length == 0)return true;if (length > circularBufferReadAllow(cb))return false;if (cb->readPointer + length <= cb->end)//不需要返回开头的情况{memcpy(buffer, cb->readPointer, length);cb->readPointer += length;}else//需要返回开头的情况{size_t endLength = cb->end - cb->readPointer + 1;//结尾部分需要拷贝的数量size_t beginLength = length - endLength;//开头部分需要拷贝的数量memcpy(buffer, cb->readPointer, endLength);//拷贝后面的memcpy(buffer + endLength, cb->buffer, beginLength);//拷贝前面的cb->readPointer = cb->buffer + beginLength;}return true;
}bool circularBufferDelete(CircularBuffer* cb, size_t length)
{if (length == 0)return true;if (length > circularBufferReadAllow(cb))return false;if (cb->readPointer + length <= cb->end)//不需要返回开头的情况{memset(cb->readPointer, 0x00, length);//不赋值也无所谓cb->readPointer += length;}else//需要返回开头的情况{size_t endLength = cb->end - cb->readPointer + 1;//结尾部分需要无视的数量size_t beginLength = length - endLength;//开头部分需要无视的数量memset(cb->readPointer, 0x00, endLength);//不赋值也无所谓memset(cb->buffer, 0x00, beginLength);//不赋值也无所谓cb->readPointer = cb->buffer + beginLength;}return true;
}bool circularBufferFindAndMove(CircularBuffer* cb, uint8_t* target, size_t length)
{if (target == NULL) return false;if (length == 0) return true;if (length > circularBufferReadAllow(cb)) return false;//目标长度超过当前数据长度,错误uint8_t* it = cb->readPointer;//遍历时的迭代器while (it != cb->writePointer){//目前不涉及迭代器返回开头的情况if (it + length <= cb->end){if (memcmp(it, target, length) == 0){cb->readPointer = it;return true;}}else{size_t endLength = cb->end - it + 1;//结尾部分需要比较的数量size_t beginLength = length - endLength;//开头部分需要比较的数量if (memcmp(it, target, endLength) == 0 && memcmp(cb->buffer, target + endLength, beginLength) == 0){cb->readPointer = it;return true;}}it++;if (it > cb->end) it = cb->buffer;//回到开头}return false;
}

后记

如果嵌入式项目中涉及大量数据结构的问题,请使用ETL库

ETL(Embedded Template Library) 是一个专为嵌入式系统和资源受限环境设计的轻量级 C++ 模板库,提供了类似 STL(标准模板库)的容器和算法,但更注重 确定性内存管理零动态内存分配低开销

etl::circular_buffer实现了循环缓冲区

参考

嵌入式模板库(ETL):为嵌入式系统量身定制的C++模板库-CSDN博客

参考资料

什么是循环缓冲区?-CSDN博客

https://docs.keysking.com/docs/stm32/example/UART_COMMAND

https://www.bilibili.com/video/BV1p75yzSEt9/?spm_id_from=333.337.search-card.all.click&vd_source=a56c06558fd43f4777ff9ae6c06c3e71
8E%E7%BC%96%E8%AF%91%E6%97%B6%E7%A1%AE%E5%AE%9A%E5%A4%A7%E5%B0%8F%E7%9A%84%E5%AE%B9%E5%99%A8%E5%92%8C%E7%AE%97%E6%B3%95%EF%BC%8C%E9%81%BF%E5%85%8D%E4%BA%86%E5%8A%A8%E6%80%81%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%EF%BC%8C%E7%A1%AE%E4%BF%9D%E4%BA%86%E4%BB%A3%E7%A0%81%E5%9C%A8%E8%B5%84%E6%BA%90%E6%9C%89%E9%99%90%E7%8E%AF%E5%A2%83%E4%B8%8B%E7%9A%84%E7%A8%B3%E5%AE%9A%E6%80%A7%E5%92%8C%E5%8F%AF%E9%A2%84%E6%B5%8B%E6%80%A7%E3%80%82)

参考资料

什么是循环缓冲区?-CSDN博客

https://docs.keysking.com/docs/stm32/example/UART_COMMAND

https://www.bilibili.com/video/BV1p75yzSEt9/?spm_id_from=333.337.search-card.all.click&vd_source=a56c06558fd43f4777ff9ae6c06c3e71

相关文章:

  • MUC基本知识
  • 基于javaweb的SpringBoot小说阅读系统设计与实现(源码+文档+部署讲解)
  • Threejs中顶视图截图
  • Python dotenv 使用指南:轻松管理项目环境变量
  • Bento4的安装和简单转码
  • Linux基础指令【上】
  • 写时拷贝讲解
  • dubbo 隐式传递
  • Python项目实践:控制台银行系统与词频统计工具开发指南
  • 【project】--模拟搭建一个中小型校园网的网络平台
  • SpringBoot 常用注解通俗解释
  • 何恺明团队又发新作!!-用于物理推理的去噪哈密顿网络
  • Linux基础命令总结
  • Set的学习
  • 论文如何降低AIGC?(完整指南版)
  • 【Linux系统篇】:信号的生命周期---从触发到保存与捕捉的底层逻辑
  • 长途骑行装备攻略:VELO维乐 Angel Revo坐垫伴我畅享旅途
  • arcpy列表函数的应用
  • ClickHouse查询执行与优化
  • Linux基础篇、第4章_03系统磁盘高级管理LVM 逻辑卷管理器
  • 荣盛发展去年亏损约84.43亿元,要“过苦日子、紧日子”
  • 只在上海!德国剧团新作亚洲首演:一张古典与流行的声音网络
  • 习近平在中共中央政治局第二十次集体学习时强调,坚持自立自强,突出应用导向,推动人工智能健康有序发展
  • 中方在IMF发声:美滥施关税威胁全球金融稳定,对新兴市场和发展中国家构成严峻挑战
  • 商务部:汽车流通消费改革试点正在加快推进
  • 最新研究挑战男性主导说:雌性倭黑猩猩联盟对付雄性攻击,获得主导地位