排序举例:
一个4G内存的系统,一个文件存储了9亿条不重复的9位数,现在要对这个文件进行排序
方法1:内存中进行排序(快速排序,插入排序,堆排序,归并排序等)
我们来分析下,9位数需要用int或者unsigned int类型来表示,4个字节,9亿 * 4字节= 36亿字节 = 3433M内存,对于4G内存的系统,访问3.4G的内存,难度很大,因为这种方法行不通
方法2:在数据库中进行排序
只需要读取文件后导入数据库,进行排序即可。缺点:要有数据库等设备
方法3:使用位运算进行排序
9位数的范围也就是0-999999999,一共10亿个数字,如果我们为每一个数字用一个位(bit)来表示存在还是不存在,一共只需要10亿位= 1.25亿字节 = 119M,这样很容易申请内存。
具体办法:申请一块10亿位(1.25亿字节)的数组,全部初始化为0。依次读取文件中的数字,把该数字对应的位上标记为1表示已存在。比如数字987654321,在内存中移位到987654321对应的位,设置为1。最后,从低位到高位(或从高位到低位)遍历整个数组,为1的说明存在该数,写入到文件中即可
优点:我们用一个位(bit)来表示一个9位数,而不是一个int(32bit),这样数据量压缩了32倍
缺点:只适用于不重复的数字排序,因为一旦有重复,一个位(bit)为1时,我们没法知道有多少个重复的数字。
核心代码:
1. 检测指定字符的指定位的数是否为1
// 判断字符dest的第second位是否为1
bool IsOne (unsigned char dest, int second)
{
const static int mark[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
if (second>=8 || second <0)
{
return false;
}
return (dest & mark[second]) == mark[second];
}
2. 将指定字符的指定位的数置为1
// 将字符dest的第second位改为1
bool SetOne (unsigned char* dest, int second)
{
const static int mark[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
if (second>=8 || second <0)
{
return false;
}
*dest |= mark[second];
return true;
}
去重举例1:
一个文件中有10亿条中国地区的手机号码,我们要删除其中重复的手机号码
分析如下:
一个手机号码有11位,优化一下,我们认为第一位永远是1,不予考虑,只需检测后面的10位数字
10位数字的范围是0-9999999999,一共100亿个数字
按照上面的办法,以一个位来表示一个数字,申请一块10亿位(10亿bit = 119M)的数组
依次读取文件,处理某个数字时,如果对应位置为0则标记为1;如果对应位置为1则说明数字重复,不需处理
去重举例2:
40亿个的QQ号(QQ号用unsigned int表示),要求去重,只给1G内存
分析如下
unsigned int是用4个字节表示,范围0-4294967295,差不多43亿个数字
按照上面的办法,以一个位来表示一个数字,申请一块43亿位(43亿bit = 512M)的数组
依次读取QQ号,处理某个数字时,如果对应位置为0则标记为1;如果对应位置为1则说明数字重复,不需处理
注意事项
由于操作系统或者编程语言本身的限制,有可能系统内存足够,仍然无法分配一块连续大内存的情况,这样的话可以申请多块稍微小一点的内存,然后用链表或其他的方式连接起来使用,只是判断或者标记的时候需要计算一下具体的位置