环形数组
环形数组就是利用一个数组,当到达尾部时,重新将插入指针指向头部,就像绿色部分一样,形成了一个环。
为什么要使用环形数组
1、环的大小初始已经知道,且可以预分配好
2、对数组的任意位置的操作时间复杂度都是O(1)
应用场景
在程序的开发中,我们会常常用到环形数组,比如可以作为流控,也可以作为其他的各种用途
在基于事件回调的量化交易系统中,比如有一个以30天为周期的回测,或则以多长时间内限制多少指令,超过则拒绝。
程序实现:
数据结构:
一个简单的缓冲区buffer数组
class ring_dbuffer
{
public:
ring_dbuffer()
: _pdata(nullptr)
, _item_size(0)
, _max_item_count(0)
, _item_count(0)
, _back_index(0);
~ring_dbuffer();
protected:
unsigned char* _pdata; // 环
size_t _item_size; // 每个环中格子的大小
size_t _max_item_count; // 总共环中有多少个格子
size_t _item_count; // 当前环有几个格子有效
size_t _back_index; // 当前操作到的格子的下表
size_t _nhalf_size; // 环的总长度,作为双环时的偏移
bool _is_use_seq_data; // 是否需要输出环的顺序数据指针
}
接口:
// 初始化
void reinit(size_t max_item_count, bool is_use_seq_buffer);
// 插入数据
T* push_back(const T* pbuffer);
// 取环头的数据
T* back();
// 取最环尾的数据
T* front();
// 环的大小
size_t size();
// 环是否满了
bool is_full();
// 清空环
void clear();
实现:
环初始化:
其中max_item_count,是指定一个环有多少个格子,is_use_seq_buffer是指定是否需要获取输入的连续内存块。后面会解释怎么实现。
inline void reinit(size_t max_item_count, bool is_use_seq_buffer)
{
if (max_item_count > 0)
{
if (_pdata)
{
delete[] _pdata;
_pdata = nullptr;
}
_item_size = sizeof(T);
_max_item_count = max_item_count;
_back_index = max_item_count - 1;
_item_count = 0;
if (is_use_seq_buffer)
_pdata = new unsigned char[_item_size * max_item_count * 2];
else
_pdata = new unsigned char[_item_size * max_item_count];
_nhalf_size = _item_size * max_item_count;
_is_use_seq_data = is_use_seq_buffer;
clear();
}
else
{
assert(0);
}
}
压入环:
inline T* push_back(const T* pbuffer)
{
_back_index++;
if (_back_index == _max_item_count)
{
_back_index = 0;
}
unsigned char* p = _pdata + _item_size * _back_index;
memcpy(p, pbuffer, _item_size);
if (_is_use_seq_data && is_full())
memcpy(p + _nhalf_size, pbuffer, _item_size);
if (_item_count < _max_item_count)
{
_item_count++;
}
return (T*)p;
}
环头和环尾怎么取
inline T* back() {
return (T*)(_pdata + _back_index * _item_size);
}
inline T* front() {
if (_item_count < _max_item_count || (_back_index + 1) == _max_item_count)
{
return (T*)(_pdata);
}
return (T*)(_pdata + (_back_index + 1) * _item_size);
}
特定位置的访问
inline T* at(size_t i) {
if (_back_index < i)
{
return (T*)(_pdata + (_max_item_count + _back_index - i) * _item_size);
}
return (T*)(_pdata + (_back_index - i) * _item_size);
}
获取连续的插入内存
其中,有一个变量_is_use_seq_data ,控制是否取完整的指针序列数据,如果设置了需要序列的数据,也就是reinit的最后一个参数时true,你使用front() 返回的指针,其地址往后的数组大小的数据都将是你压入进去的,有些应用场景,会需要这种连续的内存。
这就是_is_use_seq_data 变量的作用,也就是为了获取一个连续的内存空间。