说明
该专栏的上一篇文章记录了一种缓冲区的实现,可以先移步阅读 记录 队列数据缓冲区(FiFoBuffer)
总的来说,缓冲区的设计基本思路大同小异。甚至申请一块内存,通过memcpy memmove两个函数就能实现基本的缓存功能(就是有点粗糙,效率没那么高)。因此根据应用解决的问题场景不同,所以某些细节实现、改进优化也是有所不同。
此环形缓存借鉴Linux内核的Kfifo队列实现,这里修改了代码,封装为缓冲区类,可以直接复制使用。这里必须赞一下,编程是门艺术,Linux内核代码写的确实牛逼,代码简洁、清晰、巧妙、高效,很少花里胡哨的骚操作,直达本质。同时字里行间体现深厚的计算机底层功力,难以望其项背。
代码
相关环形缓存的文章较多,原理也并不是很复杂。不同的是,Linux内核的Kfifo实现个人认为做到了比较极致的境界,不在赘述,直接上代码。
- RingBuffer.h
#ifndef RINGBUFFER_H
#define RINGBUFFER_H
#include <mutex>
//判断x是否是2的次方
#define is_power_of_two(x) ((x) != 0 && (((x) & ((x) - 1)) == 0))
//取a和b中最小值
#define Min(a, b) (((a) < (b)) ? (a) : (b))
//环形缓存
class RingBuffer
{
public:
RingBuffer();
~RingBuffer();
//根据传入size 初始化缓冲区
bool initBuffer(uint32_t bufferSize);
//释放缓冲区
void freeBuffer();
//重置缓冲区(不需要重新申请内存)
void resetBuffer();
//缓存区是否为空
bool isEmpty();
//缓存区是否已满
bool isFull();
//返回可读数据长度
uint32_t getReadableLen();
//返回剩余空闲长度
uint32_t getRemainLen();
//返回缓冲区总长度
uint32_t getBufferSize();
//缓存区写入数据, 返回实际写入大小
uint32_t writeBuffer(char *inBuf, uint32_t inSize);
//缓存区读出数据 返回实际读出大小
uint32_t readBuffer(char *outBuf, uint32_t outSize);
private:
uint8_t *buffer; //缓冲区
uint32_t bufferSize; //缓冲区总大小
uint32_t write; //写入位置
uint32_t read; //读出位置
std::mutex mutex; //互斥锁
};
#endif // RINGBUFFER_H
- RingBuffer.cpp
#include "ringbuffer.h"
RingBuffer::RingBuffer()
{
buffer = nullptr;
bufferSize = 0;
write = 0;
read = 0;
}
RingBuffer::~RingBuffer()
{
freeBuffer();
}
bool RingBuffer::initBuffer(uint32_t size)
{
//需要保证为2的次幂 取余运算转换为与运算 提升效率,即write%bufferSize == write &(bufferSize-1)
if (!is_power_of_two(size))
{
if (size < 2)
size = 2;
//向上取2的次幂
int i = 0;
for (; size != 0; i++)
size >>= 1;
size = 1U << i;
}
std::lock_guard<std::mutex> lock(mutex);
buffer = new uint8_t[size];
if(buffer == nullptr)
return false;
memset(buffer, 0, size);
bufferSize = size;
write = 0;
read = 0;
return true;
}
void RingBuffer::freeBuffer()
{
std::lock_guard<std::mutex> lock(mutex);
bufferSize = 0;
write = 0;
read = 0;
if (buffer != nullptr)
{
delete[] buffer;
buffer = nullptr;
}
}
void RingBuffer::resetBuffer()
{
std::lock_guard<std::mutex> lock(mutex);
write = 0;
read = 0;
memset(buffer, 0, bufferSize);
}
bool RingBuffer::isEmpty()
{
std::lock_guard<std::mutex> lock(mutex);
return write == read;
}
bool RingBuffer::isFull()
{
std::lock_guard<std::mutex> lock(mutex);
return bufferSize == (write - read);
}
uint32_t RingBuffer::getReadableLen()
{
std::lock_guard<std::mutex> lock(mutex);
return write - read;
}
uint32_t RingBuffer::getRemainLen()
{
std::lock_guard<std::mutex> lock(mutex);
return bufferSize - (write - read);
}
uint32_t RingBuffer::getBufferSize()
{
std::lock_guard<std::mutex> lock(mutex);
return bufferSize;
}
uint32_t RingBuffer::writeBuffer(char *inBuf, uint32_t inSize)
{
std::lock_guard<std::mutex> lock(mutex);
if(buffer == nullptr || inBuf == nullptr || inSize == 0)
return -1;
//写入数据大小和缓冲区剩余空间大小 取最小值为最终写入大小
inSize = Min(inSize, bufferSize - (write - read));
//写数据如果写到末尾仍未写完的情况,那么回到头部继续写
uint32_t len = Min(inSize, bufferSize - (write & (bufferSize - 1)));
//区间为写指针位置到缓冲区末端
memcpy(buffer + (write & (bufferSize - 1)), inBuf, len);
//回到缓冲区头部继续写剩余数据
memcpy(buffer, inBuf + len, inSize - len);
//无符号溢出则为 0
write += inSize;
return inSize;
}
uint32_t RingBuffer::readBuffer(char *outBuf, uint32_t outSize)
{
std::lock_guard<std::mutex> lock(mutex);
if(buffer == nullptr || outBuf == nullptr || outSize == 0)
return -1;
//读出数据大小和缓冲区可读数据大小 取最小值为最终读出大小
outSize = Min(outSize, write - read);
//读数据如果读到末尾仍未读完的情况, 那么回到头部继续读
uint32_t len = Min(outSize, bufferSize - (read & (bufferSize - 1)));
//区间为读指针位置到缓冲区末端
memcpy(outBuf, buffer + (read & (bufferSize - 1)), len);
//回到缓冲区头部继续读剩余数据
memcpy(outBuf + len, buffer, outSize - len);
//无符号溢出则为 0
read += outSize;
return outSize;
}