算法:基于RingBuffer的高效环形数组

环形数组

环形数组就是利用一个数组,当到达尾部时,重新将插入指针指向头部,就像绿色部分一样,形成了一个环。

为什么要使用环形数组

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 变量的作用,也就是为了获取一个连续的内存空间。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值