C++ 环形缓存区的实现
简单介绍:
环形缓冲区就是在最开始的时候申请一个大buffer,有一个读指针,一个写指针,随着数据写入和读取改变读写指针,具体分为三总情况:
1、是读写速度差不多,这种情况比较简单。
2、写的很快读的慢、这种情况写指针很快回头追上了读指针,这时候就会出现写buffer覆盖掉读指针的内存块,如果还继续读取数据那么数据就会错乱,如果存储视频里相当于界面变花了。这时候我们就用到了内存映射链表,我们通过内存映射链表把读指针移动到下一个完整的内存块上,实际上就是丢帧。这样就能够保证数据的完整性。
3、就是读速度快,这种情况也比较简单,直接返回即可。下面我们来分析一下我的具体读写代码:
buffer的大小根据读写指针的快慢合理设置,可以提高内存利用,节约空间资源
环形缓冲区的优缺点:
优点
整个软件声明周期都是使用的这个环形缓冲区不用重新释放和分配空间,这样节省了时间提高了效率。
缺点
由于对环形缓冲区的空间大小是最开始就定义好的,如果这个大小估计不准确,太大的话浪费空间,太小的话,就得重新分配空间装下新的数据,这就和普通缓冲区没区别了。所以,其实环形缓冲区在服务器编程中的大部分情况下都是很少使用的。
环形缓存区结构上虽然是环形的,但本质上还是线性的,只是通过移动读写指针达到环形的效果
所以:只有当存储空间的分配/释放非常频繁 并且确实产生了明显 的影响,你才应该考虑环形缓冲区的使用。一般在视频接收和发送的时候会使用
注意:环形缓冲区一般都是非线程安全的。
实现代码
#ifndef _CIRCLE_BUFFER_HPP_ #define _CIRCLE_BUFFER_HPP_ #include<assert.h> template<typename T> class CircleBuffer { public: //构造函数 CircleBuffer(size_t size) { m_nBufSize = size; m_nReadPos = 0; m_nWritePos = 0; m_pBuf = new T[m_nBufSize]; m_bEmpty = true; m_bFull = false; } //析构函数 ~CircleBuffer() { if (m_pBuf) { delete[] m_pBuf; m_pBuf = nullptr; } } //缓存区是否满 bool isFull() { return m_bFull; } //判空 bool isEmpty() { return m_bEmpty; } //清空缓存区 void Clear() { m_nReadPos = 0; m_nWritePos = 0; m_bEmpty = true; m_bFull = false; } //获取写入内存的大小 int GetLength() { if (m_bEmpty) { return 0; } else if (m_bFull) { return m_nBufSize; } else if (m_nReadPos < m_nWritePos) { return m_nWritePos - m_nReadPos; } else { return m_nBufSize - m_nReadPos + m_nWritePos; } } //向缓存区中写数据,返回实际写入的字节数 int Write(T* buf, int count) { if (count <= 0) return 0; m_bEmpty = false; // 缓冲区已满,不能继续写入 if (m_bFull) { return 0; } // 缓冲区为空时 else if (m_nReadPos == m_nWritePos) { /* == 内存模型 == (empty) m_nReadPos (empty) |----------------------------------|-----------------------------------------| m_nWritePos m_nBufSize */ //计算剩余可写入的空间 int leftcount = m_nBufSize - m_nWritePos; if (leftcount > count) { memcpy(m_pBuf + m_nWritePos, buf, count); //尾部位置偏移 m_nWritePos += count; m_bFull = (m_nWritePos == m_nReadPos); return count; } else { //先把能放下的数据放进缓存区去 memcpy(m_pBuf + m_nWritePos, buf, leftcount); //写指针位置偏移,如果写指针右边的区域能放下剩余数据,就偏移到cont-leftcount位置, //否则就偏移到读指针位置,表示缓存区满了,丢弃多余数据 m_nWritePos = (m_nReadPos > count - leftcount) ? count - leftcount : m_nWritePos; //沿着结尾的位置开辟新内存写入剩余的数据 memcpy(m_pBuf, buf + leftcount, m_nWritePos); m_bFull = (m_nWritePos == m_nReadPos); return leftcount + m_nWritePos; } } // 有剩余空间,写指针在读指针前面 else if (m_nReadPos < m_nWritePos) { /* == 内存模型 == (empty) (data) (empty) |-------------------|----------------------------|---------------------------| m_nReadPos m_nWritePos (leftcount) */ // 计算剩余缓冲区大小(从写入位置到缓冲区尾) int leftcount = m_nBufSize - m_nWritePos; if (leftcount > count) // 有足够的剩余空间存放 { //写入缓存区 memcpy(m_pBuf + m_nWritePos, buf, count); //尾部位置偏移 m_nWritePos += count; m_bFull = (m_nReadPos == m_nWritePos); assert(m_nReadPos <= m_nBufSize); assert(m_nWritePos <= m_nBufSize); return count; } // 写指针右边剩余空间不足以放下数据 else { // 先填充满写指针右边的剩余空间,再看读指针左边能否放下剩余数据 memcpy(m_pBuf + m_nWritePos, buf, leftcount); //写指针位置偏移,如果读指针左边的区域能放下剩余数据,就偏移到cont-leftcount位置, //否则就偏移到读指针位置,表示缓存区满了,丢弃多余数据 m_nWritePos = (m_nReadPos >= count - leftcount) ? count - leftcount : m_nReadPos; //沿着结尾位置开辟新内存写入剩余数据 memcpy(m_pBuf, buf + leftcount, m_nWritePos); m_bFull = (m_nReadPos == m_nWritePos); assert(m_nReadPos <= m_nBufSize); assert(m_nWritePos <= m_nBufSize); return leftcount + m_nWritePos; } } //读指针在写指针前面 else { /* == 内存模型 == (unread) (read) (unread) |-------------------|----------------------------|---------------------------| m_nWritePos (leftcount) m_nReadPos */ int leftcount = m_nReadPos - m_nWritePos; if (leftcount > count) { // 有足够的剩余空间存放 memcpy(m_pBuf + m_nWritePos, buf, count); m_nWritePos += count; m_bFull = (m_nReadPos == m_nWritePos); assert(m_nReadPos <= m_nBufSize); assert(m_nWritePos <= m_nBufSize); return count; } else { // 剩余空间不足时要丢弃后面的数据 memcpy(m_pBuf + m_nWritePos, buf, leftcount); m_nWritePos += leftcount; m_bFull = (m_nReadPos == m_nWritePos); assert(m_bFull); assert(m_nReadPos <= m_nBufSize); assert(m_nWritePos <= m_nBufSize); return leftcount; } } } //从缓冲区读数据,返回实际读取的字节数 int Read(T* buf, int count) { if (count <= 0) return 0; m_bFull = false; // 缓冲区空,不能继续读取数据 if (m_bEmpty) { return 0; } // 缓冲区满时 else if (m_nReadPos == m_nWritePos) { /* == 内存模型 == (data) m_nReadPos (data) |--------------------------------|--------------------------------------------| m_nWritePos m_nBufSize */ int leftcount = m_nBufSize - m_nReadPos; if (leftcount > count) { memcpy(buf, m_pBuf + m_nReadPos, count); m_nReadPos += count; m_bEmpty = (m_nReadPos == m_nWritePos); return count; } else { memcpy(buf, m_pBuf + m_nReadPos, leftcount); //如果写指针左边的区域能读出剩余数据,就偏移到count-leftcount位置,否则就偏移到 //写指针位置,表示缓存区空了 m_nReadPos = (m_nWritePos > count - leftcount) ? count - leftcount : m_nWritePos; memcpy(buf + leftcount, m_pBuf, m_nReadPos); m_bEmpty = (m_nReadPos == m_nWritePos); return leftcount + m_nReadPos; } } // 写指针在前(未读数据是连续的) else if (m_nReadPos < m_nWritePos) { /* == 内存模型 == (read) (unread) (read) |-------------------|----------------------------|---------------------------| m_nReadPos m_nWritePos m_nBufSize */ int leftcount = m_nWritePos - m_nReadPos; int c = (leftcount > count) ? count : leftcount; memcpy(buf, m_pBuf + m_nReadPos, c); m_nReadPos += c; m_bEmpty = (m_nReadPos == m_nWritePos); assert(m_nReadPos <= m_nBufSize); assert(m_nWritePos <= m_nBufSize); return c; } // 读指针在前 else { /* == 内存模型 == (unread) (read) (unread) |-------------------|----------------------------|---------------------------| m_nWritePos m_nReadPos m_nBufSize */ int leftcount = m_nBufSize - m_nReadPos; // 需要读出的数据是连续的,在读指针右边 m_nReadPos<=count<=m_nBufSize if (leftcount > count) { memcpy(buf, m_pBuf + m_nReadPos, count); m_nReadPos += count; m_bEmpty = (m_nReadPos == m_nWritePos); assert(m_nReadPos <= m_nBufSize); assert(m_nWritePos <= m_nBufSize); return count; } // 需要读出的数据是不连续的,分别在读指针右边和写指针左边 else { //先读出读指针右边的数据 memcpy(buf, m_pBuf + m_nReadPos, leftcount); //位置偏移 //读指针位置偏移,如果写指针左边的区域能读出剩余数据,就偏移到cont-leftcount位置, //否则就偏移到写指针位置,表示缓存区空了,丢弃多余数据 m_nReadPos = (m_nWritePos >= count - leftcount) ? count - leftcount : m_nWritePos; //在读出写指针左边的数据 memcpy(buf, m_pBuf, m_nReadPos); m_bEmpty = (m_nReadPos == m_nWritePos); assert(m_nReadPos <= m_nBufSize); assert(m_nWritePos <= m_nBufSize); return leftcount + m_nReadPos; } } } int GetReadPos() { return m_nReadPos; } int GetWritePos() { return m_nWritePos; } private: bool m_bEmpty, m_bFull; //环形缓存区头指针 T * m_pBuf=nullptr; //环形缓存区大小 size_t m_nBufSize; //可读指针位置(头) int m_nReadPos; //可写指针位置(尾) int m_nWritePos; }; #endif // !_CIRCLE_BUFFER_HPP_
测试代码
#include<iostream> #include<string> #include"CircleBuffer.hpp" using namespace std; int main() { //---写指针在前,写指针右边剩余内存能写下数据 cout << "写指针在前,写指针右边剩余内存能写下数据" << endl; { CircleBuffer<char> A(8); char In[5] = "city"; cout << A.Write(In, 5) << endl; cout << A.GetReadPos() << endl; cout << A.GetWritePos() << endl; char Out[10] = {}; if (A.Read(Out, 5)) { cout << Out << endl; } cout << A.GetLength() << endl; } //---写指针在前,写指针右边剩余内存写不下数据,但是读指针左边内存够写下数据 { cout << "写指针在前,写指针右边剩余内存写不下数据,但是读指针左边内存够写下数据" << endl; CircleBuffer<char> A(8); char In1[2] = { '1','2' }; char In2[2] = { '3','4' }; char In3[2] = { '5','6' }; A.Write(In1, 2) ; A.Write(In2, 2) ; A.Write(In3, 2) ; char Out[10] = {}; if (A.Read(Out, 2)) { cout << Out << endl; } cout << "ReadPos= " << A.GetReadPos() << endl; cout << "WritePos= " << A.GetWritePos() << endl; char In4[2] = { '7','8' }; A.Write(In4, 2); cout << "ReadPos= " << A.GetReadPos() << endl; cout << "WritePos= " << A.GetWritePos() << endl; if (A.Read(Out, 8)) { //12345678 cout << Out << endl; } cout << A.GetLength() << endl; } //---写指针在前,写指针右边剩余内存写不下数据,读指针左边内存也不够写下数据 cout << "写指针在前,写指针右边剩余内存写不下数据,读指针左边内存也不够写下数据" << endl; CircleBuffer<char> A(8); char In1[2] = { '1','2' }; char In2[2] = { '3','4' }; char In3[2] = { '5','6' }; A.Write(In1, 2); A.Write(In2, 2); A.Write(In3, 2); char Out[10] = {}; if (A.Read(Out, 2)) { cout << Out << endl; } cout << "ReadPos= " << A.GetReadPos() << endl; cout << "WritePos= " << A.GetWritePos() << endl; char In4[4] = { '7','8','9','0' }; A.Write(In4, 4); cout << "ReadPos= " << A.GetReadPos() << endl; cout << "WritePos= " << A.GetWritePos() << endl; if (A.Read(Out, 8)) { //12345678 cout << Out << endl; } cout << A.GetLength() << endl; //---读指针在前,读指针右边剩余内存能读出数据 //---读指针在前,读指针右边剩余内存不能完整读出数据,但是加上写指针左边内存能完整读出数据 //---读指针在前,读指针右边剩余内存不能完整读出数据,加上写指针左边内存也不能完整读出数据 system("pause"); return 0; }
等风起的那一天,我已准备好一切