一、顺序读取存放号码文件的中全部号码,并取139以后的八位转换为int类型;每读取号码数满一百万个(这个数据可配置)将已经读取的号码排序并存入新建的临时文件。 二、将全部生成的号码有序的临时文件合并存入结果文件。这个算法虽然解决了空间问题,可是运行效率极低,因为IO读写操做太多,加上步骤1中的排序的算法(快速排序)原本效率就不高(对于电话排序这种特殊状况来讲),致使1亿条数据排序运行3个小时才有结果。
一、初始化bits[capacity]; 二、顺序全部读入电话号码,并转换为int类型,修改位向量值:bits[phoneNum]=1; 三、遍历bits数组,若是bits[index]=1,转换index为电话号码输出。因为Java中没有 bit 类型,一个 boolean 值占空间为 1byte(感兴趣的能够本身写程序验证),我本身写了個用 int 模拟 bit 数组的类,代码以下:
public class BitArray { private int[] bits = null; private int length; //用于设置或者提取int类型的数据的某一位(bit)的值时使用 private final static int[] bitValue = { 0x80000000,//10000000 00000000 00000000 00000000 0x40000000,//01000000 00000000 00000000 00000000 0x20000000,//00100000 00000000 00000000 00000000 0x10000000,//00010000 00000000 00000000 00000000 0x08000000,//00001000 00000000 00000000 00000000 0x04000000,//00000100 00000000 00000000 00000000 0x02000000,//00000010 00000000 00000000 00000000 0x01000000,//00000001 00000000 00000000 00000000 0x00800000,//00000000 10000000 00000000 00000000 0x00400000,//00000000 01000000 00000000 00000000 0x00200000,//00000000 00100000 00000000 00000000 0x00100000,//00000000 00010000 00000000 00000000 0x00080000,//00000000 00001000 00000000 00000000 0x00040000,//00000000 00000100 00000000 00000000 0x00020000,//00000000 00000010 00000000 00000000 0x00010000,//00000000 00000001 00000000 00000000 0x00008000,//00000000 00000000 10000000 00000000 0x00004000,//00000000 00000000 01000000 00000000 0x00002000,//00000000 00000000 00100000 00000000 0x00001000,//00000000 00000000 00010000 00000000 0x00000800,//00000000 00000000 00001000 00000000 0x00000400,//00000000 00000000 00000100 00000000 0x00000200,//00000000 00000000 00000010 00000000 0x00000100,//00000000 00000000 00000001 00000000 0x00000080,//00000000 00000000 00000000 10000000 0x00000040,//00000000 00000000 00000000 01000000 0x00000020,//00000000 00000000 00000000 00100000 0x00000010,//00000000 00000000 00000000 00010000 0x00000008,//00000000 00000000 00000000 00001000 0x00000004,//00000000 00000000 00000000 00000100 0x00000002,//00000000 00000000 00000000 00000010 0x00000001 //00000000 00000000 00000000 00000001 }; public BitArray(int length) { if(length < 0){ throw new IllegalArgumentException("length必须大于零!"); } bits = new int[length / 32 + (length % 32 > 0 ? 1 : 0)]; this.length = length; } //取index位的值 public int getBit(int index){ if(index <0 || index > length){ throw new IllegalArgumentException("length必须大于零小于" + length); } int intData = bits[index/32]; return (intData & bitValue[index%32]) >>> (32 - index%32 -1); } //设置index位的值,只能为0或者1 public void setBit(int index,int value){ if(index <0 || index > length){ throw new IllegalArgumentException("length必须大于零小于" + length); } if(value!=1&&value!=0){ throw new IllegalArgumentException("value必须为0或者1"); } int intData = bits[index/32]; if(value == 1){ bits[index/32] = intData | bitValue[index%32]; }else{ bits[index/32] = intData & ~bitValue[index%32]; } } public int getLength(){ return length; } }
bit 数组有了,剩下就是算法代码,核心代码以下: html
bitArray = new BitArray(100000000); //顺序读取全部的手机号码 while((phoneNum = bufferedReader.readLine())!=null){ //取139号码的最后8位数字 phoneNum = phoneNum.trim().substring(3); //将后8位转换为int类型 phoneNumAsInt = Integer.valueOf(phoneNum); //设置对应bit值为1 bitArray.setBit(phoneNumAsInt, 1); } //遍历bit数组输出全部存在的号码 for(int i = 0;i<sortUnit;i++){ if(bitArray.getBit(i)==1){ writer.write("139" + leftPad(String.valueOf(i + sortUnit*times), 8)); writer.newLine(); } } writer.flush();经测试,修改后的算法排序时只须要20多兆的内存,壹亿条电话号码排序只要10分钟(时间主要花在IO上),看来效果仍是很明显的。
这个算法很快,不过也有他的局限性: java
一、只能用于整数的排序,或者能够准确映射到正整数(对象不一样对应的正整数也不相同)的数据的排序。 二、不能处理重复的数据,重复的数据排序后只有一条,若是有这种需求,能够在这个算法的基础上修改,给出现次数大于1的数据添加個计数器便可,而后存入Map中。 三、对于数据量极其大的数据处理可能仍是比较占用空间,这种状况可配合多通道排序算法解决。这个算法的思想源于《 编程珠玑》中的 布隆过滤器(Bloom Filter),有兴趣的同窗能够读读那本书,很是不错! http://book.douban.com/subject/1230206/
在平常生活中,包括在设计计算机软件时,咱们常常要判断一个元素是否在一个集合中。好比在字处理软件中,须要检查一个英语单词是否拼写正确(也就是要判断它 是否在已知的字典中);在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上;在网络爬虫里,一个网址是否被访问过等等。最直接的方法就是将集合中所有的元素存在计算机中,遇到一个新 元素时,将它和集合中的元素直接比较便可。通常来说,计算机中的集合是用哈希表(hash table)来存储的。它的好处是快速准确,缺点是费存储空间。当集合比较小时,这个问题不显著,可是当集合巨大时,哈希表存储效率低的问题就显现出来了。好比说,壹個像 Yahoo,Hotmail 和 Gmai 那样的公众电子邮件(email)提供商,老是须要过滤来自发送垃圾邮件的人(spamer)的垃圾邮件。一个办法就是记录下那些发垃圾邮件的 email 地址。因为那些发送者不停地在注册新的地址,全世界少说也有几十亿个发垃圾邮件的地址,将他们都存起来则须要大量的网络服务器。若是用哈希表,每存储一亿 个 email 地址, 就须要 1.6GB 的内存(用哈希表实现的具体办法是将每个 email 地址对应成一个八字节的信息指纹, 而后将这些信息指纹存入哈希表,因为哈希表的存储效率通常只有 50%,所以一个 email 地址须要占用十六个字节。一亿个地址大约要 1.6GB, 即十六亿字节的内存)。所以存贮几十亿个邮件地址可能须要上百 GB 的内存。除非是超级计算机,通常服务器是没法存储的。 算法
今天,咱们介绍一种称做布隆过滤器的数学工具,它只须要哈希表 1/8 到 1/4 的大小就能解决一样的问题。 shell
布隆过滤器是由巴顿*布隆于1970年提出的。它其实是一个很长的二进制向量和一系列随机映射函数。咱们经过上面的例子来讲明起工做原理。
假定咱们存储一亿个电子邮件地址,咱们先创建一个十六亿二进制(比特),即两亿字节的向量,而后将这十六亿个二进制所有设置为零。对于每个电子邮件地址 X,咱们用八个不一样的随机数产生器(F1,F2, ...,F8) 产生八个信息指纹(f1, f2, ..., f8)。再用一个随机数产生器 G 把这八个信息指纹映射到 1 到十六亿中的八个天然数 g1, g2, ...,g8。如今咱们把这八个位置的二进制所有设置为一。当咱们对这一亿个 email 地址都进行这样的处理后。一个针对这些 email 地址的布隆过滤器就建成了。详见下图: 编程
如今,让咱们看看如何用布隆过滤器来检测一个可疑的电子邮件地址 Y 是否在黑名单中。咱们用相同的八个随机数产生器(F1, F2, ..., F8)对这个地址产生八个信息指纹 s1,s2,...,s8,而后将这八个指纹对应到布隆过滤器的八个二进制位,分别是 t1,t2,...,t8。若是 Y 在黑名单中,显然,t1,t2,..,t8 对应的八个二进制必定是一。这样在遇到任何在黑名单中的电子邮件地址,咱们都能准确地发现。
布隆过滤器决不会漏掉任何一个在黑名单中的可疑地址。可是,它有一条不足之处。也就是它有极小的可能将一个不在黑名单中的电子邮件地址断定为在黑名单中,由于有可能某个合理使用的邮件地址正巧对应个八个都被设置成一的二进制位。好在这种可能性很小。咱们把它称为误识几率。在上面的例子中,误识几率在万分之一如下。
布隆过滤器的好处在于快速,省空间。可是有必定的误识别率。常见的补救办法是在创建一个小的白名单,存储那些可能别误判的邮件地址。 数组