簡單介紹:
環形緩沖區就是在最開始的時候申請一個大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; }