前言
之前研究红黑树,也就是索引相关的操作,写好了对索引数据的增删改查功能。为了测试算法的正确性和稳定性,选择了测试唯一索引,因为集合索引只造成数据的重复,并且向程序插入512M(约5亿)个唯一的数据,生成这么多的数据,如果是递增或者递减的,这种数据很好生成,但要生成随机的数据,并且互不相等,就需要花点时间来研究算法。
我用的是C语言来实现的,所以,相关描述也是针对C语言的
实现方案
1. 用rand函数生成随机数,如果结果缓冲区不存在,放进去
这种方法,在数量比较少的情况下,比如10个数据,100个数据,也许能实现。但在数量比较多的情况下,即使能实现,耗时也大大超过了计划。
rand()生成的随机数,范围是0~65535,一共65536个数,但不是说,调用了rand() 65536次后,0到65535的数一定都会出现一次。因为是随机性的,并且是统计概率,有可能调用1亿次,每个数才能都出现。
另外,由于判断数是否已经存在缓冲区,虽然可以用二分法来实现,但是,如果要生成的数据很多,比如5亿个数据,对这些数据都判断,也要消耗很多时间。
总结,这个方案理论可行,实际不可行。
2. 洗牌法,模拟手工洗牌,把已排好序的唯一数据洗乱
这种方法,可以初始化一个排行序的数组,再用类似洗牌的方法,对这个数组洗乱。
初始化的代码如下。
int iArray[65536];
int iTmp;
//初始化数据
for (iTmp = 0; iTmp < 65536; iTmp++)
{
iArray[iTmp] = iTmp;
}
手工洗牌的方法很多,无法就是取出一部分数据,放到牌的最前面或者最后面,以达到牌序变乱,洗牌的次数越多,牌序就越乱。
我开始就是这么做的,但是,分配了5个亿的数据,也就是512M个,每个数据32b,也就是4B,总数据量达到2GB,洗一次牌就相当于拷贝了2GB的数据,而内存拷贝数据的速度也就8GB每秒左右,1秒能洗4次的牌。并且,这么大的数据量,不可能只洗1次,起码100万次,这样下来,耗时25万秒,相当于3天时间,这个时间还是太长了。
总结,这个方法,在数据量比较少的情况下,是可以实现的,但数据量比较多,即使能实现,也不符合实际。
3. 抽签法,从排好序的唯一数据中,通过抽签来取出一个数据,放到结果中
这个方法,也是从洗牌法里面想出来的,不过是一种很装X的洗牌,平时手工洗牌就是上面那种,但有些人,就喜欢把牌往天上撒,等牌掉下来,再一张一张捡起来叠好,这样牌序就是乱的。
这种想法,最后演变成抽签法。初始化一个排好序的数组,通过rand()生成随机数,如果数据量超过65536,则生成2个随机数,第一个数乘以65536再加上第二给随机数,对剩下的数据取模,把结果作为下标,从剩下的数据取出来,放到缓冲区,再将末尾或者前面的数据移到被抽出的数据中。
比如当前数据组是iArrayData[],开始位置是iIndex1,数据量一共是iTotal,通过rand()生成的两个数是r1和r2,则抽出的数据的相对下标是 iIndex2=iTotal%(r1*65536+r2),则在数组中的绝对下标是iIndex3=iIndex1+iIndex2,需要把iData1=iArrayData[iIndex3]取出来,放到结果缓冲区的末尾。
大概代码如下
#define DATA_SIZE 512*1024*1024 //定义数据量为512M
int iArrayData[DATA_SIZE];//数据数组为512M个数据
int iArrayRlt[DATA_SIZE]; //结果数组数据
int iRltSize = 0; //结果数组下标
int iTmp;
int iIndex1;
int iIndex2;
int iIndex3;
int r1;
int r2;
int iData1;
//初始化数据
for (iTmp = 0; iTmp < DATA_SIZE; iTmp++)
{
iArrayData[iTmp] = iTmp;
}
int iTotal = DATA_SIZE; //剩余的数据量
//从初始化的数组中抽出数据
iIndex1 = 0;
for (iTmp = 0; iTmp < DATA_SIZE; iTmp++)
{
r1 = rand(); //第一个随机数
r2 = rand(); //第二个随机数
iIndex2 = iTotal/(r1*65536+r2);//相对下标
iIndex3 = iIndex2+iIndex1; //绝对下标
iArrayRlt[iTmp] = iArrayData[iIndex3];//取出数据,放到结果缓冲区末尾
//原来第一个数据移到被抽出的数据中
if (iIndex1 != iIndex3)//如果iIndex2==0,取的就是当前数据的第一个,否则,是其他位置的数据,就要将第一个数据放到其他数据的位置
{
iArrayData[iIndex3] = iArrayData[iIndex1];
}
//将第一个数据位置加1
iIndex2++;
//将剩余数据量减1
iTotal--;
}
//数据生成完毕
这个方案,主要时间在rand()里面了,其他操作基本不耗时间,实际上,生成5亿个完全不一样的数据,大概花了60秒。
实际的代码如下
unsigned int *piArray;
unsigned int *piResult;
unsigned int iSize;
int iRandomTmp;
unsigned int iMillitm;
unsigned int iLeftSize;
unsigned int iHigh1;
unsigned int iHigh2;
unsigned int iLow1;
unsigned int iLow2;
unsigned int iStartIndex;
unsigned int iEndIndex;
unsigned int iIndex;
unsigned int iFlag;
unsigned int iBase;
void createRandom(int iaSize)
{
iSize = iaSize;
/*初始化*/
piArray = malloc( sizeof(int) * iSize );
if (piArray == NULL)
{
fprintf( stderr, "get ram[%lu] fail\n", sizeof(int) * iSize );
return 0;
}
for (iRandomTmp = 0; iRandomTmp < iSize; iRandomTmp++)
{
*(piArray + iRandomTmp) = iRandomTmp;
}
/*初始化*/
piResult = malloc( sizeof(int) * iSize );
if (piResult == NULL)
{
fprintf( stderr, "get ram[%lu] fail\n", sizeof(int) * iSize );
return 0;
}
memset( piResult, 0x00, sizeof(int) * iSize );
iLeftSize = iSize;
iStartIndex = 0;
iEndIndex = iSize - 1;
iFlag = 0;
for (iRandomTmp = 0; iRandomTmp < iSize; iRandomTmp++)
{
iMillitm = getMillitm();
iHigh1 = (((unsigned int )rand()) * iMillitm)& 0x000000FF; /*取低8位*/
iHigh2 = (((unsigned int )rand()) * iMillitm) >> 8; /*取高8位*/
iLow1 = (((unsigned int )rand()) * iMillitm)& 0x000000FF; /*取低8位*/
iLow2 = (((unsigned int )rand()) * iMillitm) >> 8; /*取高8位*/
iIndex = ((iHigh1 << 24) + (iHigh2 << 16) + (iLow1 << 8) + iLow2)%iLeftSize;
*(piResult + iRandomTmp) = *(piArray + iIndex + iStartIndex) + iBase;
if (iFlag == 0)
{
*(piArray + iIndex + iStartIndex) = *(piArray + iEndIndex);
iEndIndex--;
iFlag == 1;
}
else
{
*(piArray + iIndex + iStartIndex) = *(piArray + iStartIndex);
iStartIndex++;
iFlag = 0;
}
iLeftSize--;
}
}