关于实现ID池的小讨论

公众号:程序员波波
在这里插入图片描述
只是个人在开发中遇到一些小问题,可能没有解决得足够好,但是也是一次思考的过程。

场景

在开发的时候遇到一种情况,就是对于一系列的请求,我需要全部加入一个请求队列,然后通过统一的处理程序处理完毕后,放入结果队列。但是发出请求的程序,如果想从结果队列中取出自己请求的结果,需要一个全局唯一的ID,用以区分不同请求的结果。


一些尝试

对于这个问题,会有一些很直接的想法,比如:(1)用个count计数器,每次请求获得count这个ID,然后count加一;(2)对于每次请求,获取请求的当前时间,然后通过一些哈希,然后得到一个ID。

对于第一种想法

其实,就是事情固定了ID的一个范围或者说是取值,然后不断从这个范围内取数当做ID。
但是缺点就是count是无限往后加的,如果不考虑存储的局限,确实是全局唯一的。但是要想实现无限,需要更多的内存来保存这个ID值,得不偿失。
但是如果仅用一个表示范围有限的变量来存储,必然到后面会出现溢出,这样会与之前的ID冲突。

总结一下

就是ID不能有效的重复利用,因为ID只用于一次请求和结果,结束后应当释放此次的ID。


对于第二种想法

可能来说用时间哈希确实在一定程度是全局唯一的。但是哈希在数据量很大的时候,必然会出现冲突。
时间上来说要想区分不同时间,必然需要更精确的表示时间,在能精确表示的前提下,需要的内存将更多。
在有限的时间精度下,进行哈希,需要处理哈希冲突。这种情况下,可能需要维护哈希表,时间和空间上也就有了一定的要求。

总结一下

这种想法依赖于时间的精度或哈希,最大的问题还是冲突的问题。


尝试对第一种想法进行改进

第一种方法的缺点在于没有对ID进行重复利用。于是,我尝试对第一种想法进行改进,尝试对之前的ID进行维护。

数据结构的选取

想要维护历史释放的ID,必然需要通过一些数据结构进行维护。

链表?

其实count的活动空间就是一个线性空间,可以假想成一个数组。
在一系列ID释放的操作后,理论上这个数组中未被占用的元素可能是一段一段的。可以用链表维护这样一段一段的的范围。

节点元素:空闲区间。
初始状态:一个节点,0到最大值。
获取一个ID:遍历链表,得到第一个空闲区间,去掉区间中的第一个数作为ID。如果区间变成空区间,需要去掉这个节点。
释放一个ID:遍历链表,直到ID在两个相邻区间中间,插入这个数。如果相邻区间内合并,需要将区间合并成一个。

总结一下

时间复杂度上来说,获取和释放的最好情况都是O(1)的,但是最差可能变成线性的复杂度。
空间上来说,最好也是O(1),但是最差也可能变成线性的。


队列或栈?

与链表不同,队列或者栈是直接用来保存被释放的节点。

元素:被释放元素。
初始状态:count = 0, 队列或栈为空。
获取一个ID:先判断队列或者栈为空,不为空就直接从中获取,如果为空,就获取count,然后count加一。
释放一个ID:如果这个ID是count-1,就让count减一,否则就放入队列或者栈中。

总结一下

与链表不同的是,这种方法至少维护了一个count到最大值的连续区间,剩余所有的数,都保存在队列或者栈中。
时间复杂度上来说,获取和释放都是O(1)的。
空间复杂度上来说,很大程度上可能会成为线性的。

队列和栈的选取

假设所有请求的服务时间都是相同的话,而且是先来先服务,那么理论上用栈是更好的,因为这种情况下,第二次被获取的ID,释放顺序应该是大致倒序的,这样可以最大可能的等于count-1,这样就可以减少栈中的元素了。
当然队列应该也是有自身的好处的,本次不细研究队列和栈的优缺点,下面将以栈为例讨论。


代码的实现

下面的讨论,本次不考虑多线程情况。
首先因为ID是全局唯一的。所以需要实现单例模式。

类中的数据

class IdPool
{
protected:
	static IdPool;
	int count;
	stack<int> rest;
};
IdPool *IdPool::idPool = NULL;

idPool一开始应该初始化为空指针。


构造函数

protected:
	IdPool()
	{
		count = 0;
	}

构造函数应该是private或者protected的,防止用户创建对象。


单例模式

public:
	static IdPool *getIdPool()
	{
		if (idPool == NULL)
			idPool = new IdPool();
		return idPool;
	}

用户通过getIdPool获得idPool对象。


获取ID

int getId()
{
	if (rest.empty())
	{
		count++;
		return count-1;
	}
	else
	{
		int id = rest.top();
		rest.pop();
		return id;
	}
}

释放ID

void freeId(int id)
{
	if (id == count-1) count--;
	else rest.push(id);
}

未完待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值