关于海量数据查找排序问题

关于海量数据处理

常用的数据结构:

1.Bloom Filter

大致思想是这样,把一个数据通过N个哈希函数映射到一个长度为M的数组的一位上,将hash函数对应的值的位数组置1,查找时如果发现所有hash函数对应位都是1说明该数据的存在。但不能保证完全正确性,但是此方法无比高效。

【实例】给你A,B两个文件,各存放50亿条URL,每条URL占用64字节,内存限制是4G,让你找出A,B文件共同的URL。如果是三个乃至n个文件呢?

2.哈希法

这个简单,无非是通过一些哈希函数把元素搞到一个指定的位置,简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。这个很一般啊感觉。无非就是分类查找么,完全不如1猛。

3.最大或最小堆

就是一个完全的最大或最小二叉树,用途,比如:1)100w个数中找最大的前100个数。 用一个100个元素大小的最小堆即可。感觉还是不错的。

4.Bit-map

所谓的Bit-map就是用一个bit位来标记某个元素对应的Value, 而Key即是该元素。由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省。


问题1:

假设一个文件中有9亿条不重复的9位整数,现在要求对这个文件进行排序。

一般解题思路:
1、将数据导入到内存中
2、将数据进行排序 (比如插入排序、快速排序)
3、将排序好的数据存入文件

但是问题是:一个整数为4个字节,即使使用数组也需要900,000,000 * 4byte = 3.4G内存。对于32位系统,访问2G以上的内存非常困难,而且一般设备也没有这么多的物理内存,将数据完全导入到内存中的做法不现实 。

其他解决办法:
1、导入数据库运算
2、分段排序运算
3、使用bit位运算

解决方案一:数据库排序

将文本文件导入到数据库,让数据库进行索引排序操作后提取数据到文件。

优点:操作简单
缺点:运算速度慢,而且需要数据库设备。

解决方案二:分段排序

操作方式:
规定一个内存大小,比如200M,200M可以记录(200*1024*1024/4) = 52428800条记录,我们可以每次提取5000万条记录到文件进行排序,要装满9位整数需要20次,所以一共要进行20次排序,需要对文件进行20次读操作

缺点: 编码复杂,速度也慢(至少20次搜索)

关键步骤:
先将整个9位整数进行分段,亿条数据进行分成20段,每段5000万条,在文件中依次搜索0~5000万,50000001~1亿……将排序的结果存入文件。

解决方案三:bit位操作

思考下面的问题:
一个最大的9位整数为999999999,这9亿条数据是不重复的,可不可以把这些数据组成一个队列或数组,让它有0~999999999(10亿个)元素。数组下标表示数值,节点中用0表示这个数没有,1表示有这个数。

判断0或1只用一个bit存储就够了。声明一个可以包含9位整数的bit数组(10亿),一共需要10亿/8=120M内存。把内存中的数据全部初始化为0, 读取文件中的数据,并将数据放入内存。比如读到一个数据为341245909这个数据,那就先在内存中找到341245909这个bit,并将bit值置为1遍历整个bit数组,将bit为1的数组下标存入文件。

问题2:

现在有一万(1-10000)的个数,从中拿掉一个数,还剩9999个数,现在用一个数组来存储这9999个数,问怎么才能找出拿掉的数?

用10000个数的数组循环匹配9999个数,匹配成功,从9999数组中去除,不成功就是该数。

问题3:

在某个项目中,我们需要对2亿条手机号码删除重复记录(过滤号码黑名单同样有效)。

工作难点就在于如何处理这2亿条电话号码,直接用哈希表存放手机号码不大现实,即使经过优化,,用一个unsigned int存放一条记录,那也得需要2亿*4=8亿byte,远超过32位系统的寻址能力。

解决方案:

将电话号码由12位单个数字组成的字符串转换为一个unsigned int型数据(这个完全可能,手机号码由前三位数字和后面八位数字组成,后面八位需要占到1~1000万的空间,而前面用0~100的数字存储已经足够) ,为简单起见,默认为0~4G的数字都有可能分布号码,为此我们分配4G/32=512M的内存,将这2亿个号码整理成unsigned int类型后按上述办法存放在这块内存中(比如13512345678我们整理后为112345678,我们找到内存中112345678bit的下标,并将此bit值设为1) ,遍历整个bit数组,记录下所有的号码,这些号码即是不重复的手机号码。

总结:

建立一个足够大的bit数组当作hash表,以bit数组的下标来表示一个整数,以bit位中的0或1来表示这个整数是否在这个数组中存在,适用于无重复原始数据的搜索,原来每个整数需要4byte空间变为1bit,空间压缩率为32倍,扩展后可实现其他类型(包括重复数据)的搜索。

注意:由于操作系统和编程语言本身的限制,有可能内存足够,但无法分配一块连续大内存的情况,这样的话可以申请多块稍微小一点的内存,然后用链表或其他的方式连接起来使用。

问题4:

已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数。

8位最多99 999 999,大概需要99M个bit,大概10几m字节的内存即可。 (可以理解为从0-99 999 999的数字,每个数字对应一个Bit位,所以只需要99M个Bit==1.2MBytes,这样,就用了小小的1.2M左右的内存表示了所有的8位数的电话)。

问题5:

2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。

将bit-map扩展一下,用2bit表示一个数即可,0表示未出现,1表示出现一次,2表示出现2次及以上,在遍历这些数的时候,如果对应位置的值是0,则将其置为1;如果是1,将其置为2;如果是2,则保持不变。或者我们不用2bit来进行表示,我们用两个bit-map即可模拟实现这个2bit-map,都是一样的道理。

公司的一道考试题算法分析

题目大意:移动公司需要对已经发放的所有139段的号码进行统计排序,已经发放的139号码段的文件都存放在一个文本文件中(原题是放在两个文件中),一个号码一行,现在需要将文件里的所有号码进行排序,并写入到一个新的文件中;号码可能会有很多,最多可能有一亿个不同的号码(所有的139段号码),存入文本文件中大概要占1.2G的空间;jvm最大的内存在300以内,程序要考虑程序的可执行性及效率;只能使用Java标准库,不得使用第三方工具。

这是个典型的大数据量的排序算法问题,首先要考虑空间问题,一下把1.2G的数据读入内存是不太可能的,就算把1一亿条数据,转都转换成int类型存储也要占接近400M的空间。

《编程珠玑》时上面也有个问题的需求这个这个题目差不多,记得好像使用是位向量(实际上就是一个bit数组),用电话作为index,这里我们也可以使用位向量:用位向量存储电话号码,一个号码占一个bit,一亿个电话号码也只需要大概12M的空间;算法大概如下:

1.初始化bits[capacity];

2.顺序所有读入电话号码,并转换为int类型,修改位向量值:bits[phoneNum]=1;

3.遍历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数组有了,剩下就是算法代码,核心代码如下:

bitArray = new BitArray(100000000);  
     //顺序读取所有的手机号码
     while((phoneNum = bufferedReader.readLine())!=null){
      phoneNum = phoneNum.trim().substring(3);//13573228432
      //取139后8位转换为int类型
      phoneNumAsInt = Integer.valueOf(phoneNum);
      //设置对应bit值为1
      bitArray.setBit(phoneNumAsInt, 1);
     }  
     //遍历bit数组输出所有存在的号码
     for(int i = 0;iif(bitArray.getBit(i)==1){
           writer.write("139" + leftPad(String.valueOf(i + sortUnit*times), 8));
           writer.newLine();            
         }      
     }
     writer.flush();

经测试,修改后的算法排序时只需要20多M的内存,一亿条电话号码排序只要10分钟(时间主要花在IO上),看来效果还是很明显的。

这个算法很快,不过也有他的局限性:

1.只能用于整数的排序,或者可以准确映射到正整数(对象不同对应的正整数也不相同)的数据的排序。

2.不能处理重复的数据,重复的数据排序后只有一条(如果有这种需求可以在这个算法的基础上修改,给出现次数大于1的数据添加个计数器,然后存入Map中)

3.对于数据量极其大的数据处理可能还是比较占用空间,这种情况可配合多通道排序算法解决。

你可能感兴趣的:(求职准备)