# 循环缓冲区
说明
所谓消费,就是数据读取并删除。
循环缓冲区这个数据结构与生产者-消费者问题高度适配。
生产者-产生数据,消费者-处理数据,二者速度不一致,因此需要循环缓冲区。
显然,产生的数据要追加到循环缓冲区末尾,然后再去消费。
可以直接去最后拿代码,看一般使用说明。
定义
// 定义缓冲区大小(可根据需要修改)
#define CIRCULAR_BUFFER_SIZE 12// 结构体定义(对应C++类)
typedef struct {uint8_t buffer[CIRCULAR_BUFFER_SIZE]; // 内部存储数组uint8_t* bufferEnd; // 指向 buffer[capacity - 1]uint8_t* writeP; // 写指针(当前写入位置)uint8_t* readP; // 读指针(当前读取位置)
} CircularBuffer;
其中
变量 | 说明 |
---|---|
readP | 将要消费内容的位置,应当指向数据区的开头。 |
writeP | 将要追加内容的位置,应当指向数据区结尾的后一个。或者说“非数据区”的开头 |
因为循环缓冲区与生产者-消费者问题高度适配,所以这样设计是合理的。
情况1
1 | 2 | 3 | 4 | 5 | 6 | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | -> | writeP | -> | end | ||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
情况2
7 | 8 | 9 | 1 | 2 | 3 | 4 | 5 | 6 | |||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writeP | -> | readP | -> | end | ||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
后面写不下,回到开头去写。
注意消费指针、追加指针都只能“向后走”,走到最后就回到开头。
追加、消费必须在对应位置去做。
注意
不允许将整个数组全部使用,否则无法区分当前是没有数据,还是数据已满。
当然也可以定义一个变量去记录当前有多少数据,我这里不使用这种方法。
没有数据
writeP | -> | ||||||||||
readP | -> | ||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
数据充满整个数组
11 | 12 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
writeP | -> | ||||||||||
readP | |||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
初始化
/*** @brief 初始化* @param cb 循环缓冲区指针*/
void CircularBuffer_Init(CircularBuffer* cb);void CircularBuffer_Init(CircularBuffer* cb) {cb->bufferEnd = cb->buffer + CIRCULAR_BUFFER_SIZE - 1;cb->writeP = cb->buffer;cb->readP = cb->buffer;
}
是否为空
/*** @brief 是否为空* @param cb 循环缓冲区指针* @return true 是* @return false 否*/
bool CircularBuffer_Empty(const CircularBuffer* cb);bool CircularBuffer_Empty(const CircularBuffer* cb) {return cb->writeP == cb->readP;
}
数据长度
当前存储的数据长度,或者说允许消费的数据的长度
/*** @brief 数据长度* @param cb 循环缓冲区指针* @return 已存储的数据长度*/
size_t CircularBuffer_Size(const CircularBuffer* cb);size_t CircularBuffer_Size(const CircularBuffer* cb) {return (cb->writeP - cb->readP + CIRCULAR_BUFFER_SIZE) % CIRCULAR_BUFFER_SIZE)
}
总容量
/*** @brief 最大容量* @param cb 循环缓冲区指针* @return 最大容量*/
size_t CircularBuffer_Capacity(const CircularBuffer* cb);size_t CircularBuffer_Capacity(const CircularBuffer* cb) {return CIRCULAR_BUFFER_SIZE - 1;// 最大容量(总空间 - 1,因为不能完全填满)
}
数据开头位置
显然,数据头部head的位置就是readP。
/*** @brief 数据区开头* @param cb 循环缓冲区指针* @return 数据区开头指针*/
uint8_t* CircularBuffer_Begin(const CircularBuffer* cb);uint8_t* CircularBuffer_Begin(const CircularBuffer* cb) {if (CircularBuffer_Empty(cb)) return NULL;return cb->readP;
}
数据结尾位置
但要注意:数据最后一个tail并不是head + dataCount - 1!(见前面的例子)
应当这样得到
数据结尾tail
tail = buffer + ((head + dataCount -1 - buffer + CIRCULAR_BUFFER_SIZE) % CIRCULAR_BUFFER_SIZE)
其中
head + dataCount -1 - buffer 可能超过真实数组范围,是一个“假想”的索引,还要进行取模运算!得到真实的索引。
/*** @brief 数据区结尾* @param cb 循环缓冲区指针* @return 数据区结尾指针*/
uint8_t* CircularBuffer_End(const CircularBuffer* cb);uint8_t* CircularBuffer_End(const CircularBuffer* cb) {if (CircularBuffer_Empty(cb)) return NULL;size_t tailIndex = (cb->readP + CircularBuffer_Size(cb) - 1 - cb->buffer + CIRCULAR_BUFFER_SIZE) % (CIRCULAR_BUFFER_SIZE);return (cb->buffer + tailIndex);
}
指针是否指向数据区
先排除指针根本不指向内部数组的情况。
然后注意,不要比较it和tail的真实位置,应当比较它们的虚拟位置
如果itMap <= tailMap 说明指针指向数据区
if (it < cb->buffer || it > cb->bufferEnd) return false;size_t itMapIndex = (it - cb->readP) % (CIRCULAR_BUFFER_SIZE);//指针以数据头为开头的索引
size_t tailMapIndex = CircularBuffer_Size(cb) - 1;//数据尾部,以数据头为开头的索引
if (itMapIndex > tailMapIndex) return false;//目标不在数据区内,错误
it - readP 可能为超出范围,应当取模以修正
(it - readP) % (CIRCULAR_BUFFER_SIZE) 得到是,当循环缓冲区以head为开头时,it的索引。
迭代器的“后”一个
迭代器(指针)it,它的后一个itBehind
注意itBehind并不一定是it++。
遍历时应当
while(...)
{it++;if(it > bufferEnd) it=buffer;//超过范围,回到开头
}
当然,也可以这样得到后一个的位置
itBehind = buffer + (it - buffer +1) % CIRCULAR_BUFFER_SIZE
同理,后n个
itNBehind = buffer + (it - buffer + n) % CIRCULAR_BUFFER_SIZE
追加一批数据
循环缓冲区使用时,来一批数据,就追加一批。
注意:为了提高拷贝效率,需要调用memcpy。memcpy有底层的优化。
并不是迭代器一直++,因此需要区分两种情况。在真实的数组找到关键位置,然后调用memcpy
一般循环缓冲区用于生产者消费者模型,所以要使用这个和后面的消费一批数据。
说明
不需要返回开头的情况
以追加4个为例
原本
0 | 1 | 2 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
之后
0 | 1 | 2 | 3 | 4 | 5 | 6 | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
需要返回开头的情况
写不下需要返回开头!
以追加6个为例
原本
0 | 1 | 2 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
之后
5 | 6 | 7 | 8 | 0 | 1 | 2 | 3 | 4 | |||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writeP | readP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
实现代码
/*** @brief 向缓冲区追加数据* @param cb 循环缓冲区指针* @param data 数据* @param length 数据长度* @return true 成功* @return false 失败*/
bool CircularBuffer_Append(CircularBuffer* cb, const uint8_t* data, size_t length);bool CircularBuffer_Append(CircularBuffer* cb, const uint8_t* data, size_t length) {if (length == 0) return true;if (length > (CircularBuffer_Capacity(cb) - CircularBuffer_Size(cb))) return false;if (cb->writeP + length <= cb->bufferEnd) {memcpy(cb->writeP, data, length);cb->writeP += length;}else {size_t endLength = cb->bufferEnd - cb->writeP + 1;size_t beginLength = length - endLength;memcpy(cb->writeP, data, endLength);memcpy(cb->buffer, data + endLength, beginLength);cb->writeP = cb->buffer + beginLength;}return true;
}
消费一批数据
必须从头消费。
说明
以消费5个数据为例
不需要返回开头的情况
读取前
1 | 2 | 3 | 4 | 5 | 6 | 7 | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
读取后
6 | 7 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
需要返回开头的情况
读取前
3 | 4 | 5 | 6 | 7 | 1 | 2 | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writeP | readP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
读取后
6 | 7 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
实现代码
/*** @brief 从缓冲区消费数据* @param cb 循环缓冲区指针* @param data 数据* @param length 数据长度* @return true 成功* @return false 失败 */
bool CircularBuffer_Consume(CircularBuffer* cb, uint8_t* rBuffer, size_t length);bool CircularBuffer_Consume(CircularBuffer* cb, uint8_t* rBuffer, size_t length) {if (length == 0) return true;if (length > CircularBuffer_Size(cb)) return false;if (cb->readP + length <= cb->bufferEnd) {memcpy(rBuffer, cb->readP, length);cb->readP += length;}else {size_t endLength = cb->bufferEnd - cb->readP + 1;size_t beginLength = length - endLength;memcpy(rBuffer, cb->readP, endLength);memcpy(rBuffer + endLength, cb->buffer, beginLength);cb->readP = cb->buffer + beginLength;}return true;
}
删除一批数据
只给出迭代器,删除它(不含)前面的所有数据的方法。
任意位置的删除,会让数据不再连续,要移动数据,这很复杂、而且没什么用,不做讨论。
说明
显然
删除一个迭代器前面的数据,只需要把头指针移动到迭代器的位置。
例
删除前
5 | 6 | 7 | 8 | 9 | 0 | 1 | 2 | 3 | 4 | ||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | it | writeP | readP | end | |||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
删除后
7 | 8 | 9 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
实现代码
/*** @brief 删除指针前的所有数据* @param cb 循环缓冲区指针* @param it 一个指向数据区结尾指针* @return true 成功* @return false 失败*/
bool CircularBuffer_EraseFront(CircularBuffer* cb, uint8_t* it);bool CircularBuffer_EraseFront(CircularBuffer* cb, uint8_t* it) {if (CircularBuffer_Empty(cb)) return false;if (it < cb->buffer || it > cb->bufferEnd) return false;size_t itMapIndex = (it - cb->readP) % (CIRCULAR_BUFFER_SIZE);//指针以数据头为开头的索引size_t tailMapIndex = CircularBuffer_Size(cb) - 1;//数据尾部,以数据头为开头的索引if (itMapIndex > tailMapIndex) return false;//目标不在数据区内,错误cb->readP = it;return true;
}
查找数据
通常是要查找通信协议的数据头
说明
以查找1 2 3 4为例
为了说明最后一次查找的位置last,我们弄个虚拟数组,他以数据区开头位置为开头。
为了之后比较方便,我们找last后面一个的位置lastBehind
在虚拟数组中
? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ||
---|---|---|---|---|---|---|---|---|---|---|---|
readP | tail | writeP | |||||||||
1 | 2 | 3 | 4 | ||||||||
last | lastBehind | ||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
在虚拟数组中最后一次查找的后面一个的位置是
lastBehindMap = tailMap - length + 1 + 1;
迭代器不需要返回开头的情况
找到前
? | ? | 1 | 2 | 3 | 4 | ? | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
1 | 2 | 3 | 4 | ||||||||
it | -> | ||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
找到后
? | ? | 1 | 2 | 3 | 4 | ? | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
1 | 2 | 3 | 4 | ||||||||
it | |||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
迭代器需要返回开头的情况
找到前
3 | 4 | ? | ? | ? | ? | 1 | 2 | ||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writeP | readP | end | ||||||||
1 | 2 | 3 | 4 | ||||||||
it | -> | ||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
临近尾部要把目标数据拆分成两部分分别比较
3 | 4 | ? | ? | ? | 1 | 2 | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writeP | readP | end | ||||||||
4 | 1 | 2 | 3 | ||||||||
it | -> | ||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
找到后
3 | 4 | ? | ? | ? | 1 | 2 | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writeP | readP | end | ||||||||
3 | 4 | 1 | 2 | ||||||||
it | |||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
实现代码
/*** @brief 查找特定数据* @param cb 循环缓冲区指针* @param target 目标* @param length 目标长度* @return NULL 没有找到* @return 找到的目标指针
*/
uint8_t* CircularBuffer_Find(CircularBuffer* cb, const uint8_t* target, size_t length);uint8_t* CircularBuffer_Find(CircularBuffer* cb, const uint8_t* target, size_t length) {if (target == NULL) return NULL;if (length == 0) return CircularBuffer_Begin(cb);if (length > CircularBuffer_Size(cb)) return NULL;//虚拟数组中“最后一次比较的后一个”的索引size_t lastBehindMapIndex = CircularBuffer_Size(cb) - length + 1 + 1;//真实数组中“最后一次比较的后一个”的索引size_t lastBehindIndex = (cb->readP - cb->buffer + lastBehindMapIndex) % CIRCULAR_BUFFER_SIZE;//真实数组中“最后一次比较的后一个”的位置。uint8_t* lastBehind = cb->buffer + lastBehindIndex;uint8_t* it = cb->readP;while (it != lastBehind) {if (it + length <= cb->bufferEnd) {if (memcmp(it, target, length) == 0) {return it;}}else {size_t endLength = cb->bufferEnd - it + 1;size_t beginLength = length - endLength;if (memcmp(it, target, endLength) == 0 && memcmp(cb->buffer, target + endLength, beginLength) == 0) {return it;}}it++;if (it > cb->bufferEnd) it = cb->buffer;//超过范围,回到开头}return NULL;
}
完整代码
使用说明
生产者产生数据,追加进循环缓冲区->消费者查找协议头->删除前面的无效数据->消费数据包
append -> find -> eraseFront -> consume
C版本
Circularbuffer.h
#ifndef CIRCULAR_BUFFER_H
#define CIRCULAR_BUFFER_H#include <stdint.h>
#include <string.h>
#include <stdbool.h>
// 定义缓冲区大小(可根据需要修改)
#define CIRCULAR_BUFFER_SIZE 12// 结构体定义
typedef struct {uint8_t buffer[CIRCULAR_BUFFER_SIZE]; // 内部存储数组uint8_t* bufferEnd; // 指向 buffer[capacity - 1]uint8_t* writeP; // 写指针(当前写入位置)uint8_t* readP; // 读指针(当前读取位置)
} CircularBuffer;// 函数声明/*** @brief 初始化* @param cb 循环缓冲区指针*/
void CircularBuffer_Init(CircularBuffer* cb);
void CircularBuffer_Clear(CircularBuffer* cb);/*** @brief 数据长度* @param cb 循环缓冲区指针* @return 已存储的数据长度*/
size_t CircularBuffer_Size(const CircularBuffer* cb);/*** @brief 最大容量* @param cb 循环缓冲区指针* @return 最大容量*/
size_t CircularBuffer_Capacity(const CircularBuffer* cb);/*** @brief 是否为空* @param cb 循环缓冲区指针* @return true 是* @return false 否*/
bool CircularBuffer_Empty(const CircularBuffer* cb);/*** @brief 数据区开头* @param cb 循环缓冲区指针* @return 数据区开头指针*/
uint8_t* CircularBuffer_Begin(const CircularBuffer* cb);/*** @brief 数据区结尾* @param cb 循环缓冲区指针* @return 数据区结尾指针*/
uint8_t* CircularBuffer_End(const CircularBuffer* cb);/*** @brief 向缓冲区追加数据* @param cb 循环缓冲区指针* @param data 数据* @param length 数据长度* @return true 成功* @return false 失败*/
bool CircularBuffer_Append(CircularBuffer* cb, const uint8_t* data, size_t length);/*** @brief 从缓冲区消费数据* @param cb 循环缓冲区指针* @param data 数据* @param length 数据长度* @return true 成功* @return false 失败 */
bool CircularBuffer_Consume(CircularBuffer* cb, uint8_t* rBuffer, size_t length);/*** @brief 删除指针前的所有数据* @param cb 循环缓冲区指针* @param it 一个指向数据区结尾指针* @return true 成功* @return false 失败*/
bool CircularBuffer_EraseFront(CircularBuffer* cb, uint8_t* it);/*** @brief 查找特定数据* @param cb 循环缓冲区指针* @param target 目标* @param length 目标长度* @return NULL 没有找到* @return 找到的目标指针
*/
uint8_t* CircularBuffer_Find(CircularBuffer* cb, const uint8_t* target, size_t length);#endif // CIRCULAR_BUFFER_H
Circularbuffe.c
#include "CircularBuffer.h"// 初始化缓冲区(对应C++构造函数)
void CircularBuffer_Init(CircularBuffer* cb) {cb->bufferEnd = cb->buffer + CIRCULAR_BUFFER_SIZE - 1;cb->writeP = cb->buffer;cb->readP = cb->buffer;
}// 清空缓冲区
void CircularBuffer_Clear(CircularBuffer* cb) {cb->writeP = cb->buffer;cb->readP = cb->buffer;
}size_t CircularBuffer_Size(const CircularBuffer* cb) {return (cb->writeP - cb->readP + CIRCULAR_BUFFER_SIZE) % CIRCULAR_BUFFER_SIZE)
}size_t CircularBuffer_Capacity(const CircularBuffer* cb) {return CIRCULAR_BUFFER_SIZE - 1;// 最大容量(总空间 - 1,因为不能完全填满)
}bool CircularBuffer_Empty(const CircularBuffer* cb) {return cb->writeP == cb->readP;
}uint8_t* CircularBuffer_Begin(const CircularBuffer* cb) {if (CircularBuffer_Empty(cb)) return NULL;return cb->readP;
}uint8_t* CircularBuffer_End(const CircularBuffer* cb) {if (CircularBuffer_Empty(cb)) return NULL;size_t tailIndex = (cb->readP + CircularBuffer_Size(cb) - 1 - cb->buffer + CIRCULAR_BUFFER_SIZE) % (CIRCULAR_BUFFER_SIZE);return (cb->buffer + tailIndex);
}bool CircularBuffer_Append(CircularBuffer* cb, const uint8_t* data, size_t length) {if (length == 0) return true;if (length > (CircularBuffer_Capacity(cb) - CircularBuffer_Size(cb))) return false;if (cb->writeP + length <= cb->bufferEnd) {memcpy(cb->writeP, data, length);cb->writeP += length;}else {size_t endLength = cb->bufferEnd - cb->writeP + 1;size_t beginLength = length - endLength;memcpy(cb->writeP, data, endLength);memcpy(cb->buffer, data + endLength, beginLength);cb->writeP = cb->buffer + beginLength;}return true;
}bool CircularBuffer_Consume(CircularBuffer* cb, uint8_t* rBuffer, size_t length) {if (length == 0) return true;if (length > CircularBuffer_Size(cb)) return false;if (cb->readP + length <= cb->bufferEnd) {memcpy(rBuffer, cb->readP, length);cb->readP += length;}else {size_t endLength = cb->bufferEnd - cb->readP + 1;size_t beginLength = length - endLength;memcpy(rBuffer, cb->readP, endLength);memcpy(rBuffer + endLength, cb->buffer, beginLength);cb->readP = cb->buffer + beginLength;}return true;
}bool CircularBuffer_EraseFront(CircularBuffer* cb, uint8_t* it) {if (CircularBuffer_Empty(cb)) return false;if (it < cb->buffer || it > cb->bufferEnd) return false;size_t itMapIndex = (it - cb->readP) % (CIRCULAR_BUFFER_SIZE);//指针以数据头为开头的索引size_t tailMapIndex = CircularBuffer_Size(cb) - 1;//数据尾部,以数据头为开头的索引if (itMapIndex > tailMapIndex) return false;//目标不在数据区内,错误cb->readP = it;return true;
}uint8_t* CircularBuffer_Find(CircularBuffer* cb, const uint8_t* target, size_t length) {if (target == NULL) return NULL;if (length == 0) return CircularBuffer_Begin(cb);if (length > CircularBuffer_Size(cb)) return NULL;//虚拟数组中“最后一次比较的后一个”的索引size_t lastBehindMapIndex = CircularBuffer_Size(cb) - length + 1 + 1;//真实数组中“最后一次比较的后一个”的索引size_t lastBehindIndex = (cb->readP - cb->buffer + lastBehindMapIndex) % CIRCULAR_BUFFER_SIZE;//真实数组中“最后一次比较的后一个”的位置。uint8_t* lastBehind = cb->buffer + lastBehindIndex;uint8_t* it = cb->readP;while (it != lastBehind) {if (it + length <= cb->bufferEnd) {if (memcmp(it, target, length) == 0) {return it;}}else {size_t endLength = cb->bufferEnd - it + 1;size_t beginLength = length - endLength;if (memcmp(it, target, endLength) == 0 && memcmp(cb->buffer, target + endLength, beginLength) == 0) {return it;}}it++;if (it > cb->bufferEnd) it = cb->buffer;//超过范围,回到开头}return NULL;
}
C++版本
Circularbuffer.h
#pragma once#include <stddef.h>
#include <stdint.h>#define CIRCULAR_BUFFER_SIZE 12 //缓冲区大小,根据需要修改,必要的话动态吧,去构造函数中newclass CircularBuffer
{
private:uint8_t buffer[CIRCULAR_BUFFER_SIZE] = { 0x00 };//内部真正的数组uint8_t* const bufferEnd = buffer + CIRCULAR_BUFFER_SIZE - 1;//缓冲区真正的结尾uint8_t* writeP = this->buffer;//写指针,追加的位置uint8_t* readP = this->buffer;//读指针,消费数据的位置public:/*** @brief 清空此循环缓冲区* @note 可以使用此函数清空循环缓冲区*/void clear();public:/*** @brief 储存的数据量* @return 储存的数据量* @note 也就是未处理的数据长度*/size_t size();public:/*** @brief 最大容量* @return 最大容量*/size_t capacity();public:/*** @brief 是否为空* @return true 是* @return false 否*/bool empty();public:/*** @brief 数据区开头* @return 有数据 数据区开头的指针* @return 没有数据 nullptr*/uint8_t* begin();public:/*** @brief 数据区结尾* @return 有数据 数据区最后一个的指针* @return 没有数据 nullptr*/uint8_t* end();public:/*** @brief 向此缓冲区追加数据* @param data 要写入的数据指针* @param length 要写入的数据的长度* @return true 成功* @return false 失败*/bool append(const uint8_t* data, size_t length);public:/*** @brief 从此循环缓冲区中读出数据* @param rBuffer 读取到的缓冲区* @param length 要求读取的长度* @return true 成功* @return false 失败*/bool consume(uint8_t* rBuffer, size_t length);public:/*** @brief 删除迭代器前的所有数据* @param it 迭代器* @return true 成功* @return false 失败*/bool eraseFront(uint8_t* it);public:/*** @brief 查找特定数据* @param target 目标数据指针* @param length 目标数据长度* @return 找到 目标位置* @return 没有找到 nullptr*/uint8_t* find(const uint8_t* target, size_t length);public:/*** @brief 打印,测试用*/void printfCircularBuffer();
};
Circularbuffer.cpp
#include "CircularBuffer.h"
#include <string.h>void CircularBuffer::clear()
{//memset(cb, 0x00, CIRCULAR_BUFFER_SIZE);//其实不赋值也无所谓this->writeP = this->buffer;this->readP = this->buffer;
}size_t CircularBuffer::size()
{return ((this->writeP - this->readP + (this->capacity() + 1)) % (this->capacity() + 1));
}size_t CircularBuffer::capacity()
{return CIRCULAR_BUFFER_SIZE - 1;//内部数组不允许存满
}bool CircularBuffer::append(const uint8_t* data, size_t length)
{if (length == 0)return true;if (length > (this->capacity() - this->size())) return false;//超过最大长度,错误if (this->writeP + length <= this->bufferEnd)//不需要返回开头的情况{memcpy(this->writeP, data, length);this->writeP += length;}else//需要返回开头的情况{size_t endLength = this->bufferEnd - this->writeP + 1;//结尾部分需要拷贝的数量size_t beginLength = length - endLength;//开头部分需要拷贝的数量memcpy(this->writeP, data, endLength);//拷贝到后面 memcpy(this->buffer, data + endLength, beginLength);//拷贝到前面this->writeP = this->buffer + beginLength;}return true;
}bool CircularBuffer::empty()
{return this->writeP == this->readP;
}uint8_t* CircularBuffer::begin()
{if (this->empty()) return nullptr;//没有数据return this->readP;
}uint8_t* CircularBuffer::end()
{if (this->empty()) return nullptr;//没有数据size_t tailIndex = (this->readP + this->size() - 1 - this->buffer + this->capacity() + 1) % (this->capacity() + 1);return this->buffer + tailIndex;
}bool CircularBuffer::consume(uint8_t* rBuffer, size_t length)
{if (length == 0) return true;if (length > size()) return false;//超过当前存储的长度,错误if (this->readP + length <= this->bufferEnd)//不需要返回开头的情况{memcpy(rBuffer, this->readP, length);this->readP += length;}else//需要返回开头的情况{size_t endLength = this->bufferEnd - this->readP + 1;//结尾部分需要拷贝的数量size_t beginLength = length - endLength;//开头部分需要拷贝的数量memcpy(rBuffer, this->readP, endLength);//拷贝后面的memcpy(rBuffer + endLength, this->buffer, beginLength);//拷贝前面的this->readP = this->buffer + beginLength;}return true;
}bool CircularBuffer::eraseFront(uint8_t* it)
{if (this->empty()) return false;if (it<this->buffer || it>this->bufferEnd) return false;//不在真实数组范围内,错误size_t itMapIndex = (it - this->begin()) % (this->capacity() + 1);//指针以数据头为开头的索引size_t tailMapIndex = this->size() - 1;//数据尾部,以数据头为开头的索引if (itMapIndex > tailMapIndex) return false;//目标不在数据区内,错误this->readP = it;return true;
}uint8_t* CircularBuffer::find(const uint8_t* target, size_t length)
{if (target == NULL) return nullptr;if (length == 0) return this->begin();if (length > this->size()) return nullptr;//目标长度超过当前数据长度,错误size_t lastBehindMapIndex = this->size() - length + 1 + 1;//虚拟数组中“最后一次比较的后一个”的索引size_t lastBehindIndex = (this->readP + lastBehindMapIndex - this->buffer) % (this->capacity() + 1);//真实数组中“最后一次比较的后一个”的索引uint8_t* lastBehind = this->buffer + lastBehindIndex;//真实数组中“最后一次比较的后一个”的位置。uint8_t* it = this->readP;//遍历时的迭代器while (it != lastBehind){//目前不涉及迭代器返回开头的情况if (it + length <= this->bufferEnd){if (memcmp(it, target, length) == 0){ return it;}}else{size_t endLength = this->bufferEnd - it + 1;//结尾部分需要比较的数量size_t beginLength = length - endLength;//开头部分需要比较的数量if (memcmp(it, target, endLength) == 0 && memcmp(this->buffer, target + endLength, beginLength) == 0){return it;}}it++;if (it > this->bufferEnd) it = this->buffer;//超过结尾,回到开头}return nullptr;
}#include <iostream>
#include <iomanip>
void CircularBuffer::printfCircularBuffer()
{std::cout << "Size: " << this->size() << ", Capacity: " << this->capacity()<< ", Empty: " << (this->empty() ? "true" : "false") << "\n";std::cout << "buffer" << std::endl;for (size_t i = 0; i < this->capacity(); ++i) {// 将 uint8_t 转换为 int,并以两位十六进制输出std::cout << std::hex << std::uppercase<< std::setw(2) << std::setfill('0')<< static_cast<int>(this->buffer[i]) << " ";}std::cout << std::dec << std::endl; // 恢复十进制输出格式std::cout << "data" << std::endl;uint8_t* it = this->begin();for (size_t i = 0; i < this->size(); i++){// 将 uint8_t 转换为 int,并以两位十六进制输出std::cout << std::hex << std::uppercase<< std::setw(2) << std::setfill('0')<< static_cast<int>(*it) << " ";it = it + ((it - this->buffer + 1) % (this->capacity() + 1));//下一个位置}std::cout << std::dec << std::endl; // 恢复十进制输出格式std::cout << "readP index " << (this->readP - this->buffer) << std::endl;std::cout << "writeP index " << (this->writeP - this->buffer) << std::endl;std::cout << "\n\n";
}
后记
循环缓冲区一般配合生产者、消费者问题使用。
生产者产生数据,追加进循环缓冲区->消费者查找协议头->删除前面的无效数据->消费数据包
append -> find -> eraseFront -> consume
嵌入式似乎很忌讳动态内存分配(本人目前不清楚为什么)。
如果嵌入式项目中涉及大量数据结构的问题,请使用ETL库
ETL(Embedded Template Library) 是一个专为嵌入式系统和资源受限环境设计的轻量级 C++ 模板库,提供了类似 STL(标准模板库)的容器和算法,但更注重 确定性内存管理、零动态内存分配 和 低开销。
etl::circular_buffer实现了循环缓冲区
但我没看懂怎么用
参考
循环缓冲区其它讲解:
https://docs.keysking.com/docs/stm32/example/UART_COMMAND
【【STM32】串口数据拆包?黏包?循环缓冲区帮你搞定!】 https://www.bilibili.com/video/BV1p75yzSEt9/?share_source=copy_web&vd_source=734d9fe130776e8ae82b2b5371a5f5b8
ETL说明
https://blog.csdn.net/gitblog_00682/article/details/142607246?fromshare=blogdetail&sharetype=blogdetail&sharerId=142607246&sharerefer=PC&sharesource=cctv1324&sharefrom=from_link