Buffer模块
- 一、Buffer模块是什么?实现思想是什么?
- 二、代码实现
- 如何设计:
- 1.成员变量:
- 2.构造函数:
- 3.获取地址和空间大小
- 4.读写偏移向后移动
- 5.扩容函数
- 6.写入函数
- 7.读取函数
- 8.获取地址和空间大小
- 9.获取地址和空间大小
- 10.获取地址和空间大小
- 11.测试代码
- 12.整体源代码
一、Buffer模块是什么?实现思想是什么?
Buffer模块:缓冲区模块
提供的功能:存储数据,取出数据
实现思想:
- 实现缓冲区得有一块内存空间,采用vector,vertor底层其实使用的就是一个线性的内存空间
- 要素:a.默认空间大小;b.当前的读取数据位置;c.当前的写入数据位置;
- 操作:
a.写入数据:
当前写入位置指向哪里,就从哪里开始写入,如果后续剩余空闲空间不够了
考虑整体缓冲区空闲空间是否足够(因为读位置也会向后偏移,前边有可能会有空闲空间)
足够,将数据移动到起始位置即可
不够,扩容,从当前写位置开始扩容足够大小
数据一旦写入成功,当前写位置,就要向后偏移
b.读取数据
当前的读取位置指向哪里,就从哪里开始读取,前提是有数据可读,可读数据大小:当前写入位置,减去当前读取位置
二、代码实现
如何设计:
class Buffer{
private:
std::vector<char> buffer:
/*位置,是一个相对偏移量,而不是绝对地址*/
uint64_t_read_idx; // 相对读偏移量
uint64_t_write_idx;//相对写偏移量
public:
1.获取当前写位置地址
2.确保可写空间足够(移动+扩容)
3.获取前沿空闲空间大小
4.获取后沿空闲空间大小
5.将写位置向后移动指定长度
6.获取当前读位置地址
7.获取可读数据大小
8.将读位置向后移动指定长度
9.清理功能
};
1.成员变量:
#define BUFFER_DEFAULT_SIZE 1024
private:
std::vector<char> _buffer;// 使用vector进行内存空间管理
//位置,是相对偏移量,而不是一个绝对地址
uint64_t _read_idx; // 相对读偏移:此处开始读取
uint64_t _write_idx;// 相对写偏移:此处开始写入
这段代码片段是一个简单的类定义,其中包含了三个私有成员变量:
-
_buffer
:这是一个使用std::vector<char>
类型的变量,用于管理内存空间。std::vector
是C++标准库提供的动态数组容器,可以动态增长和缩小。在这里,它被用来管理一块内存缓冲区。 -
_read_idx
:这是一个uint64_t
类型的变量,表示相对读偏移量,用于指示从缓冲区中哪里开始读取数据。 -
_write_idx
:这是另一个uint64_t
类型的变量,表示相对写偏移量,用于指示在缓冲区中哪里开始写入数据。
通过这些成员变量,这个类可能用于实现一个简单的缓冲区管理器,用于读取和写入数据到内存缓冲区中。
2.构造函数:
public:
Buffer() : _read_idx(0), _write_idx(0), _buffer(BUFFER_DEFAULT_SIZE) {}
这段代码是一个构造函数的定义,它使用了成员初始化列表来初始化类的成员变量。在这个构造函数中:
_read_idx(0)
:对_read_idx
进行了初始化,将其初始值设为0。_write_idx(0)
:对_write_idx
进行了初始化,将其初始值设为0。_buffer(BUFFER_DEFAULT_SIZE)
:对_buffer
进行了初始化,使用了BUFFER_DEFAULT_SIZE
作为初始大小来创建了一个std::vector<char>
类型的缓冲区。
综合起来,这段代码表示了在实例化Buffer
类时,会自动将_read_idx
和_write_idx
初始化为0,并创建一个具有默认大小的缓冲区。
3.获取地址和空间大小
//获取起始地址
char* Begin() { return &*_buffer.begin(); }
//获取当前写入起始地址, _buffer的空间起始地址,加上写偏移量
char* WritePosition() { return Begin() + _write_idx; }
//获取当前读取起始地址
char* ReadPosition() { return Begin() + _read_idx; }
//获取缓冲区末尾空闲空间大小--写偏移之后的空闲空间, 总体空间大小减去写偏移
uint64_t TailIdleSize() { return _buffer.size() - _write_idx; }
//获取缓冲区起始空闲空间大小--读偏移之前的空闲空间
uint64_t HeadIdleSize() { return _read_idx; }
//获取可读数据大小 = 写偏移 - 读偏移
uint64_t ReadAbleSize() { return _write_idx - _read_idx; }
4.读写偏移向后移动
//将读偏移向后移动
void MoveReadOffset(uint64_t len) {
if (len == 0) return;
//向后移动的大小,必须小于可读数据大小
assert(len <= ReadAbleSize());
_read_idx += len;
}
//将写偏移向后移动
void MoveWriteOffset(uint64_t len) {
//向后移动的大小,必须小于当前后边的空闲空间大小
assert(len <= TailIdleSize());
_write_idx += len;
}
5.扩容函数
//确保可写空间足够(整体空闲空间够了就移动数据,否则就扩容)
void EnsureWriteSpace(uint64_t len) {
//如果末尾空闲空间大小足够,直接返回
if (TailIdleSize() >= len) { return; }
//末尾空闲空间不够,则判断加上起始位置的空闲空间大小是否足够, 够了就将数据移动到起始位置
if (len <= TailIdleSize() + HeadIdleSize()) {
//将数据移动到起始位置
uint64_t rsz = ReadAbleSize();//把当前数据大小先保存起来
std::copy(ReadPosition(), ReadPosition() + rsz, Begin());//把可读数据拷贝到起始位置
_read_idx = 0; //将读偏移归0
_write_idx = rsz; //将写位置置为可读数据大小, 因为当前的可读数据大小就是写偏移量
}
else {
//总体空间不够,则需要扩容,不移动数据,直接给写偏移之后扩容足够空间即可
_buffer.resize(_write_idx + len);
}
}
6.写入函数
//写入数据
void Write(const void* data, uint64_t len) {
//1. 保证有足够空间,
if (len == 0) return;
EnsureWriteSpace(len);
//2. 拷贝数据进去
const char* d = (const char*)data;
std::copy(d, d + len, WritePosition());
}
void WriteAndPush(const void* data, uint64_t len) {
Write(data, len);
MoveWriteOffset(len);
}
void WriteString(const std::string& data) {
return Write(data.c_str(), data.size());
}
void WriteStringAndPush(const std::string& data) {
WriteString(data);
MoveWriteOffset(data.size());
}
void WriteBuffer(Buffer& data) {
return Write(data.ReadPosition(), data.ReadAbleSize());
}
void WriteBufferAndPush(Buffer& data) {
WriteBuffer(data);
MoveWriteOffset(data.ReadAbleSize());
}
7.读取函数
//读取数据
void Read(void* buf, uint64_t len) {
//要求要获取的数据大小必须小于可读数据大小
assert(len <= ReadAbleSize());
std::copy(ReadPosition(), ReadPosition() + len, (char*)buf);
}
void ReadAndPop(void* buf, uint64_t len) {
Read(buf, len);
MoveReadOffset(len);
}
std::string ReadAsString(uint64_t len) {
//要求要获取的数据大小必须小于可读数据大小
assert(len <= ReadAbleSize());
std::string str;
str.resize(len);
Read(&str[0], len);
return str;
}
std::string ReadAsStringAndPop(uint64_t len) {
assert(len <= ReadAbleSize());
std::string str = ReadAsString(len);
MoveReadOffset(len);
return str;
}
8.获取地址和空间大小
//查找换行字符
char* FindCRLF() {
//找到'\n'的位置
char* res = (char*)memchr(ReadPosition(), '\n', ReadAbleSize());
return res;
}
9.获取地址和空间大小
/*通常获取一行数据,这种情况针对是*/
std::string GetLine() {
char* pos = FindCRLF();
if (pos == NULL) {//没找到
return "";
}
// +1是为了把换行字符也取出来。
return ReadAsString(pos - ReadPosition() + 1);
}
std::string GetLineAndPop() {
std::string str = GetLine();
MoveReadOffset(str.size());
return str;
}
10.获取地址和空间大小
//清空缓冲区
void Clear() {
//只需要将偏移量归0即可
_read_idx = 0;
_write_idx = 0;
}
11.测试代码
(1)这段代码使用了一个名为Buffer
的类来进行一些操作。这段代码的大致意思:
- 首先,创建了一个名为
buf
的Buffer
对象。 - 然后,创建了一个字符串
str
并赋值为"hello!!"。 - 接着,调用
buf
对象的WriteStringAndPush
方法,将字符串写入缓冲区,并将其推入缓冲区。 - 然后,创建了另一个名为
buf1
的Buffer
对象。 - 接着,调用
buf1
对象的WriteBufferAndPush
方法,将buf
对象的内容写入buf1
的缓冲区,并将其推入buf1
的缓冲区。 - 紧接着,定义了一个名为
tmp
的字符串。 - 然后,调用
buf1
对象的ReadAbleSize
方法获取可读取的大小,并将这个大小作为参数传递给ReadAsStringAndPop
方法,将读取的内容转换为字符串并将其存储在tmp
中。 - 最后,使用
std::cout
输出了tmp
、buf
和buf1
的可读取大小。
总的来说,这段代码应该是在测试一个缓冲区类的读写功能。通过这些操作,可以实现数据的写入、推入、读取和弹出操作,并输出相应的结果。
Buffer buf;
std::string str = "hello!!";
buf.WriteStringAndPush(str);
Buffer buf1;
buf1.WriteBufferAndPush(buf);
std::string tmp;
tmp = buf1.ReadAsStringAndPop(buf1.ReadAbleSize());
std::cout<< tmp<< std::endl;
std::cout<< buf.ReadAbleSize()<< std::endl;
std::cout<< buf1.ReadAbleSize()<< std::endl;
输出结果:
(2)这段代码创建了一个名为buf
的Buffer
对象,并通过循环向该缓冲区中写入300个带有编号的字符串。然后,从缓冲区中读取可读取的大小,并将其转换为字符串后存储在tmp
中,最后将tmp
输出到标准输出流。
在循环中,每次迭代都会创建一个新的std::string
,其内容为"hello"加上当前循环变量i
的值,再加上换行符\n
。这样就生成了类似"hello0\n"、“hello1\n”、"hello2\n"等格式的字符串,然后调用buf
对象的WriteStringAndPush
方法将这些字符串写入缓冲区并推入缓冲区。
最后,通过调用buf
对象的ReadAbleSize
方法获取可读取的大小,并将这个大小作为参数传递给ReadAsStringAndPop
方法,将读取的内容转换为字符串并将其存储在tmp
中。最终,将tmp
输出到标准输出流中。
这段代码的作用是往缓冲区中写入一系列带编号的字符串,然后读取整个缓冲区中的内容并输出到控制台。
Buffer buf;
for (int i = 0; i < 300; i++) {
std::string str = "hello" + std::to_string(i) + '\n';
buf.WriteStringAndPush(str);
}
std::string tmp;
tmp = buf.ReadAsStringAndPop(buf.ReadAbleSize());
std::cout << tmp << std::endl;
输出结果:hello0-hello299
12.整体源代码
// Buffer //
#include <iostream>
#include <vector>
#include <string>
#include <cassert>
#include <cstring>
#include <ctime>
#define BUFFER_DEFAULT_SIZE 1024
class Buffer
{
private:
std::vector<char> _buffer;// 使用vector进行内存空间管理
//位置,是相对偏移量,而不是一个绝对地址
uint64_t _read_idx; // 相对读偏移:此处开始读取
uint64_t _write_idx;// 相对写偏移:此处开始写入
public:
Buffer() : _read_idx(0), _write_idx(0), _buffer(BUFFER_DEFAULT_SIZE) {}
//获取起始地址
char* Begin() { return &*_buffer.begin(); }
//获取当前写入起始地址, _buffer的空间起始地址,加上写偏移量
char* WritePosition() { return Begin() + _write_idx; }
//获取当前读取起始地址
char* ReadPosition() { return Begin() + _read_idx; }
//获取缓冲区末尾空闲空间大小--写偏移之后的空闲空间, 总体空间大小减去写偏移
uint64_t TailIdleSize() { return _buffer.size() - _write_idx; }
//获取缓冲区起始空闲空间大小--读偏移之前的空闲空间
uint64_t HeadIdleSize() { return _read_idx; }
//获取可读数据大小 = 写偏移 - 读偏移
uint64_t ReadAbleSize() { return _write_idx - _read_idx; }
//将读偏移向后移动
void MoveReadOffset(uint64_t len) {
if (len == 0) return;
//向后移动的大小,必须小于可读数据大小
assert(len <= ReadAbleSize());
_read_idx += len;
}
//将写偏移向后移动
void MoveWriteOffset(uint64_t len) {
//向后移动的大小,必须小于当前后边的空闲空间大小
assert(len <= TailIdleSize());
_write_idx += len;
}
//确保可写空间足够(整体空闲空间够了就移动数据,否则就扩容)
void EnsureWriteSpace(uint64_t len) {
//如果末尾空闲空间大小足够,直接返回
if (TailIdleSize() >= len) { return; }
//末尾空闲空间不够,则判断加上起始位置的空闲空间大小是否足够, 够了就将数据移动到起始位置
if (len <= TailIdleSize() + HeadIdleSize()) {
//将数据移动到起始位置
uint64_t rsz = ReadAbleSize();//把当前数据大小先保存起来
std::copy(ReadPosition(), ReadPosition() + rsz, Begin());//把可读数据拷贝到起始位置
_read_idx = 0; //将读偏移归0
_write_idx = rsz; //将写位置置为可读数据大小, 因为当前的可读数据大小就是写偏移量
}
else {
//总体空间不够,则需要扩容,不移动数据,直接给写偏移之后扩容足够空间即可
_buffer.resize(_write_idx + len);
}
}
//写入数据
void Write(const void* data, uint64_t len) {
//1. 保证有足够空间,
if (len == 0) return;
EnsureWriteSpace(len);
//2. 拷贝数据进去
const char* d = (const char*)data;
std::copy(d, d + len, WritePosition());
}
void WriteAndPush(const void* data, uint64_t len) {
Write(data, len);
MoveWriteOffset(len);
}
void WriteString(const std::string& data) {
return Write(data.c_str(), data.size());
}
void WriteStringAndPush(const std::string& data) {
WriteString(data);
MoveWriteOffset(data.size());
}
void WriteBuffer(Buffer& data) {
return Write(data.ReadPosition(), data.ReadAbleSize());
}
void WriteBufferAndPush(Buffer& data) {
WriteBuffer(data);
MoveWriteOffset(data.ReadAbleSize());
}
//读取数据
void Read(void* buf, uint64_t len) {
//要求要获取的数据大小必须小于可读数据大小
assert(len <= ReadAbleSize());
std::copy(ReadPosition(), ReadPosition() + len, (char*)buf);
}
void ReadAndPop(void* buf, uint64_t len) {
Read(buf, len);
MoveReadOffset(len);
}
std::string ReadAsString(uint64_t len) {
//要求要获取的数据大小必须小于可读数据大小
assert(len <= ReadAbleSize());
std::string str;
str.resize(len);
Read(&str[0], len);
return str;
}
std::string ReadAsStringAndPop(uint64_t len) {
assert(len <= ReadAbleSize());
std::string str = ReadAsString(len);
MoveReadOffset(len);
return str;
}
//查找换行字符
char* FindCRLF() {
//找到'\n'的位置
char* res = (char*)memchr(ReadPosition(), '\n', ReadAbleSize());
return res;
}
/*通常获取一行数据,这种情况针对是*/
std::string GetLine() {
char* pos = FindCRLF();
if (pos == NULL) {//没找到
return "";
}
// +1是为了把换行字符也取出来。
return ReadAsString(pos - ReadPosition() + 1);
}
std::string GetLineAndPop() {
std::string str = GetLine();
MoveReadOffset(str.size());
return str;
}
//清空缓冲区
void Clear() {
//只需要将偏移量归0即可
_read_idx = 0;
_write_idx = 0;
}
};