海量数据处理方法总结 & 常见大数据题目汇总

海量数据处理方法大总结

方式一:分而治之/hash映射(哈希映射) + hashmap统计 + 快速/归并/堆排序(万能方法)

这种方法是典型的“分而治之”的策略,是解决空间限制最常用的方法,即海量数据不能一次性读入内存,而我们需要对海量数据进行的计数、排序等操作。

基本思路如下图所示:

  • 步骤一:先借助哈希算法,计算每一条数据的 hash 值,按照 hash 值将海量数据分布存储到多个桶中。根据 hash 函数的唯一性,相同的数据一定在同一个桶中
  • 步骤二:如此,我们再依次处理这些小文件,最后做合并运算即可。
    在这里插入图片描述

某搜索公司一天的用户搜索词汇是海量的(百亿级别),请设计一种求出每天热门Top100词汇的可行办法(利用堆)

  • 利用哈希函数将大文件分为小文件,用hash统计每个小文件的词频,选出每个小文件的top100;
  • 将每个小文件的top100做成大根堆,然后将每个小文件的大根堆的堆顶拿出来 重新做成一个大根堆,这里称为 总堆
  • 将总堆的堆顶元素(这里记为 A)拿出来(这个元素就是所有数据中最大的),看A原本属于哪个大根堆,将原大根堆的A元素弹出,再组成一个大根堆,然后将该大根堆的堆顶元素加入总堆
  • 就这么循环往复,选取出top100

海量日志数据,统计出某日访问百度次数最多的那个IP

思路分析:IP地址最多有 2^32 = 4G 种取值情况,所以不能完全加载到内存中进行处理,采用 hash分流+ 分而治之 + 归并 的方式:

  • 按照 IP 地址的 hash(IP)%1024 值,把海量IP日志分别存储到1024个小文件中。这样每个小文件最多包含4MB个IP地址
  • 对于每一个小文件,利用HashMap统计每个IP出现的次数,然后找出频率最大的IP;
  • 然后再在这1024组最大的IP中,找出那个频率最大的IP。

有a、b两个文件,各存放50亿个URL,每个URL各占64字节,内存限制是4G,让你找出a、b文件共同的URL

思路分析:如果内存中想要存入所有的 URL,共需要 50亿 * 64= 320G大小空间,所以采用 hash 分解+ 分而治之 + 归并 的方式:

  • 遍历文件a,对每个 URL 根据某种 hash规则,求取 hash(URL)/1024,然后根据所取得的值将 URL 分别存储到1024个小文件(a0 - a1023)中,这样每个小文件的大约为300M
    • 如果hash结果很集中使得某个文件ai过大,可以再对ai进行二级hash(ai0~ai1024),这样 URL 就被hash到 1024 个不同级别的文件中。
  • 分别比较文件,a0 VS b0,…… ,a1023 VS b1023,求每对小文件中相同的 URL 时:
    • 把其中一个小文件的 URL 存储到 HashMap 中,然后遍历另一个小文件的每个 URL ;
    • 看其是否在刚才构建的 HashMap 中,如果是,那么就是共同的URL ,存到文件中。
  • 最后把1024个文件中的相同 url 合并起来。

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

思路一:hash 分解+ 分而治之 + 归并

  • 2.5亿个 int 类型 hash 到1024个小文件中 a0~a1023,如果某个小文件大小还大于内存,进行多级hash;
  • 将每个小文件读进内存,找出只出现一次的数据,输出到 b0~b1023;
  • 最后数据合并即可

思路二:BitMap 后面讲

10G的无序整型数给5G内存怎么排序?(重要 重要 重要)

思路分析:小根堆的思想,小根堆存的是 数字 以及这个数字 出现的次数,也就是两个int型整数,一共8字节,但小根堆可能还有一些索引的消耗,所以我们这里按照16字节计算

  • 那么5G内存支持 5G / 16B = 335544320 个,到离2的某次方最接近的数字,这里取 2^28 = 268435456

方案二:BitMap(位图) 与 Bloom Filter(布隆过滤器)

参考:https://my.oschina.net/freelili/blog/2885263

什么是位图(bitmap)

位图法就是bitmap的缩写。所谓bitmap,就是用每一位来存放某种状态。适用于大规模数据,但数据状态又不是很多的情况。通常是用来判断某个数据存不存在的

在java中,一个int类型占32个字节,假设有 32 个int行整数:

  • 用一个int数组来表示时为new int[32]总计占用内存 32 * 32 bit
  • 现假如我们用int字节码的每一位表示一个数字的话,那么32个数字只需要一个int类型所占内存空间大小就够了,这样在大数据量的情况下会节省很多内存。

举个例子来说明:假设我们现在有N个数,随机给定一个数,要求你快速找出这个给定的数是否在这N个数中。这个问题其实我们需要思考两个问题:

  • 问题一:如何用位图表示这N个数?
  • 问题二:如何利用位图来查找给定数是否存在于这N个数之中?

问题一:如何用位图表示这N个数?

1个int占4字节即 4 * 8 = 32位,那么我们只需要申请一个长度为 1+N/32的int型数组即可存储完这些数据,int[] tmp = new int[1+N/32] ,其中N代表要进行查找的总数,tmp 中的每个元素在内存在占32位可以对应表示十进制数0~31,所以可得到BitMap表:

  • tmp[0]:可表示0~31
  • tmp[1]:可表示32~63
  • tmp[2]可表示64~95

那么接下来就看看十进制数如何转换为对应的bit位:

假设这N个int数据为:6,3,8,32,36,…,那么具体的BitMap表示为:

在这里插入图片描述
问题二:如何利用位图来查找给定数是否存在于这N个数之中?

如何判断int数字在tmp数组的哪个下标,这个其实可以通过直接除以32取整数部分,例如:

  • 整数8除以32取整等于0,那么8就在tmp[0]上。
  • 另外,我们如何知道了8在tmp[0]中的32个位中的哪个位,这种情况直接mod上32就ok,又如整数8,在tmp[0]中的第8 mod上32等于8,那么整数8就在tmp[0]中的第八个bit位(从右边数起)。

什么是Bloom Filter(布隆过滤器)

Bloom Filter是一种空间效率很高的随机数据结构,它的原理是:

  • 当一个元素被加入集合时,通过K个Hash函数将这个元素映射成一个位阵列(Bit array)中的K个点,把它们置为1;
  • 检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:
    • 如果这些点有任何一个0,则被检索元素一定不在;
    • 如果都是1,则被检索元素很可能在。

这就是布隆过滤器的基本思想。

但Bloom Filter的这种高效是有一定代价的:

在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合(false positive)。因此,Bloom Filter不适合那些 “零错误” 的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter通过极少的错误换取了存储空间的极大节省。

给40亿个不重复的的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中(重要 重要)

这个问题其实我们需要思考两个问题:

  • 问题一:如何用位图表示这N个数?
  • 问题二:如何利用位图来查找给定数是否存在于这N个数之中?

问题一:如何用位图表示这N个数?

1个int占4字节即 4 * 8 = 32位,那么我们只需要申请一个长度为 1+N/32的int型数组即可存储完这些数据,int[] tmp = new int[1+N/32] ,其中N代表要进行查找的总数,tmp 中的每个元素在内存在占32位可以对应表示十进制数0~31,所以可得到BitMap表:

  • tmp[0]:可表示0~31
  • tmp[1]:可表示32~63
  • tmp[2]可表示64~95

那么接下来就看看十进制数如何转换为对应的bit位:

假设这N个int数据为:6,3,8,32,36,…,那么具体的BitMap表示为:

在这里插入图片描述
问题二:如何利用位图来查找给定数是否存在于这N个数之中?

如何判断int数字在tmp数组的哪个下标,这个其实可以 通过直接除以32取整数部分,例如:

  • 整数8除以32取整等于0,那么8就在tmp[0]上
  • 另外,我们如何知道了8在tmp[0]中的32个位中的哪个位,这种情况直接mod上32就ok,又如整数8,在tmp[0]中的第8 mod上32等于8,那么整数8就在tmp[0]中的第八个bit位(从右边数起)。

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

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

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

对于这种场景我可以采用2-BitMap来解决, 即为每个整数分配2bit ,用不同的0、1组合来标识特殊意思,如00表示此整数没有出现过,01表示出现一次,11表示出现过多次,就可以找出重复的整数了,其需要的内存空间是正常BitMap的2倍,为:2.5亿*2/8/1024/1024=71.5MB

具体的过程如下:

  • 扫描着3亿个整数,先查看BitMap中的对应位置,如果00则变成01,是01则变成11,是11则保持不变;
  • 当将3亿个整数扫描完之后也就是说整个BitMap已经组装完毕;
  • 最后查看BitMap将对应位为11的整数输出即可。

32位无符号整数的范围是 0 - 2^32 - 1,现在有一个正好包含40亿个无符号整数的文件,所以在整数范围中必然存在没出现的数。可以最多使用1GB的内存,怎么找到所有未出现的数?

每个数字对应1bit,0表示未出现,1表示出现过,所需内存为:2^32 / 8 / 1024 / 1024 = 512M

上一个问题进阶:只给3KB内存,找到40亿个无符号整数中的一个没出现的数即可。(范围统计思想)

  • 3KB 内存能创建多大的整形数组:3KB / 4 = 750,找到离2的某次方最接近的数字,这里取 2^9 = 512,创建长度为512的的整形数组:int[] tmp = new int[512];
  • 0 - 2^32 - 1等量的分为512份一定能整除,2^32 / 512 = 8388608,每份有8388608个数;
  • tmp[0]表示 0 ~ 8388608 - 1中的数出现多少次,tmp[1]表示 8388608 ~ 2 * 8388608 - 1中的数出现多少次。。。。。。tmp[511]表示 8388608 * 511 ~8388608 * 512 - 1中的数出现多少次;
    • x / 8388608 即可定位属于数组中的哪个数,tmp[x / 8388608 ]++
  • 只有四十亿个数,所以一定会有一个词频统计的数值不够 8388608,那么也就知道缺的数字属于哪个范围
  • 再将这个范围的数字分为 512 份,重复上面的过程,周而复始,就能得到未出现的数字

上一个问题再次进阶:只能申请有限个变量 (TODO 左程云再听一遍)

  • 0 - 2^32 - 1中进行二分,如果满了,左右应该都是 2^32 / 2 个数;
  • 在不满的区间继续二分,最终一定能将二分的范围定成一个数的范围;
  • 最多二分 32 次

32位无符号整数的范围是 0 - 2^32 - 1,现有40亿个无符号整数,可以最多使用1GB的内存,找出出现了两次的数?

思路一:hash 分解+ 分而治之 + 归并

思路二:BitMap

  • 每个数分配 2bit,00表示0次,01表示一次,10表示两次,两次及以上几位11;
  • 耗费内存:2^32 * 2 / 8 / 1024 / 1024 = 1024M = 1GB,正好够用。

现有两个各有20亿行的文件,每一行都只有一个数字,求这两个文件的交集。

解决方案一:采用 bitmap 进行问题解决,因为 int 的最大数是 2^32 = 4G,用一个二进制的下标来表示一个 int 值,大概需要4G个bit位,即约4G/8 = 512M的内存,就可以解决问题了。

  • 首先遍历文件,将每个文件按照数字的正数,负数标记到2个 bitmap 上,为:正数 bitmapA_positive,负数 bitmapA_negative;
  • 遍历另为一个文件,生成正数:bitmapB_positive,bitmapB_negative;
  • 取 bitmapA_positive and bitmapB_positive 得到2个文件的正数的交集,同理得到负数的交集;
  • 合并,问题解决。

这里一次只能解决全正数,或全负数,所以要分两次

解决方案二:Bloom Filter(布隆过滤器)

  • 依次遍历每个大文件中的每条数据,遍历每条数据时,都将它插入 Bloom Filter;
  • 如果已经存在,则在另外的集合(记为S)中记录下来;
  • 如果不存在,则插入Bloom Filter;
  • 最后,得到的S即为所有这些大文件中元素的交集

采用 Bloom Filter(布隆过滤器)会有一定的错误率

上一个问题进阶:现在不是A和B两个大文件,而是A, B, C, D….多个大文件,求集合的交集

  • 依次遍历每个大文件中的每条数据,遍历每条数据时,都将它插入 Bloom Filte

方法三:多层划分(范围统计思想)

多层划分本质上还是分而治之的思想,重在“分”的技巧上!因为元素范围很大,需要通过多次划分,逐步确定范围,然后最后在一个可以接受的范围内进行适用于:第k大,中位数,不重复或重复的数字

只给3KB内存,找到40亿个无符号整数中的一个没出现的数即可。(范围统计思想)

  • 首先根据内存来计算能申请无符号整形的最大数量:3KB 内存能创建多大的整形数组:3KB / 4 = 750,找到离2的某次方最接近的数字,这里取 2^9 = 512,创建长度为512的的整形数组:int[] tmp = new int[512]
  • 0 - 2^32 - 1等量的分为512份一定能整除,2^32 / 512 = 8388608,每份有8388608个数;
  • tmp[0]表示 0 ~ 8388608 - 1中的数出现多少次,tmp[1]表示 8388608 ~ 2 * 8388608 - 1中的数出现多少次。。。。。。tmp[511]表示 8388608 * 511 ~8388608 * 512 - 1中的数出现多少次;
    • x / 8388608 即可定位属于数组中的哪个数,tmp[x / 8388608 ]++
  • 只有四十亿个数,所以一定会有一个词频统计的数值不够 8388608,那么也就知道缺的数字属于哪个范围
  • 再将这个范围的数字分为 512 份,重复上面的过程,周而复始,就能得到未出现的数字

最多使用10MB内存,怎么找到40亿个整数的中位数?(范围统计思想)

  • 首先根据内存来计算能申请无符号整形的最大数量:10KB 内存能创建多大的整形数组:10KB / 4 = 2500,找到离2的某次方最接近的数字,这里取 2^11 = 2048,创建长度为512的的整形数组:int[] tmp = new int[2048]
  • 将40亿个整数分为2048份,2^32 / 2048 = 2097152,每份有2097152个数。tmp[0]表示0 ~ 2097152 - 1中的数出现多少次,,tmp[1]表示 2097152 ~ 2 * 2097152 - 1中的数出现多少次。。。。。。tmp[2047]表示 2097152 * 2047 ~2097152 * 2048 - 1中的数出现多少次;
  • 遍历这40亿个数进行词频统计:arr[x / 2048]++;
  • 对词频统计数组进行累加,tmp[0] + tmp[1] + 。。。+ tmp[n],一直加到刚好大于等于20亿,这样我们就知道中位数(也就是第20亿数)出现在哪个范围上,这里假设是 tmp[m];
  • 我们再将tmp[m]等分为2048份,重复上面的过程,周而复始,就能得到未出现的数字

大数据小内存排序问题:10G的有符号整数给5G内存怎么排序?(重要 重要 重要)

  • 无符号整数范围:2^32
  • 有符号整数范围:-2^31 ~ 2^31 - 1,总共也是2^32个数

方法一:小根堆(范围统计)

思路分析:小根堆的思想小根堆存的是 数字 以及这个数字 出现的次数也就是两个int型整数,一共8字节,但小根堆可能还有一些索引的消耗,所以我们这里按照16字节计算

  • 那么5G内存支持 5G / 16B = 335544320 个,到离2的某次方最接近的数字,这里取 2^28 = 268435456
  • 无符号整数的范围时:2^32 ,可以将 2^32这个范围分为:2 ^32 / 2^28 = 4,也就是分成四个范围
    • 第一个范围:-2^31 ~ -2^31 + 2^28 - 1,。。。。。,第四个范围:-2^31 + 2^28 *3 ~ 2^31 - 1
  • 依次用小根堆统计每个范围内每个数字出现的次数,也就是每次搞定一个范围的词频统计,然后输出到文件中。
    在这里插入图片描述

方法一的思考:如果有数字大量重复,比如a出现了5亿次,会有影响吗

就算a出现了5亿次,也只是词频变为5亿而已,词频也只是用一个int型来存储,不占内存
在这里插入图片描述

方法二:大根堆(维护一个门槛,即堆顶元素)

假设有10亿个数,那么我们根据内存大小维护一个大根堆,比如这里我们只能维护一个能存储500万条数据的大根堆,大根堆中存的数据和方法一中的一样,存的是 数字 以及这个数字 出现的次数,也就是两个int型整数,一共8字节。

  • 建立一个能存储500万条数据的大根堆,我们遍历这10亿个数字,找到最小的500万个数字;
    • 先往大根堆中存入500万个数,此时堆顶就是最大的数;
    • 遍历剩余的数,当有比堆顶元素小的数,那么就弹出堆顶元素,然后将该数字入堆,这样遍历完就找到了最小的500万个数。
  • 记录此时大根堆的堆顶元素,堆顶M,然后将大根堆中这500万条记录输出到文件中;
  • 再次遍历这10亿个数字,但是小于等于M的数忽略,就这么循环往复,就能完成排序。

为了方便理解,我们举一个只有十个数字的例子,并且维护一个只能存三个数字的大根堆,利用上面提到的大根堆的思想进行排序

  • 建立一个能存3条数据的大根堆,我们遍历这10个数字,找到最小的3个数字;
    • 先往大根堆中存入3个数,此时堆顶就是最大的数;
    • 遍历剩余的数,当有比堆顶元素小的数,那么就弹出堆顶元素,然后将该数字入堆,这样遍历完就找到了最小的3个数。
  • 记录此时大根堆的堆顶元素,堆顶M,然后将大根堆中这3条记录输出到文件中;
    在这里插入图片描述
  • 再次遍历这10亿个数字,但是小于等于M的数忽略,就这么循环往复,就能完成排序。
    在这里插入图片描述
    在这里插入图片描述

参考:https://blog.csdn.net/a745233700/article/details/114006686
参考:https://blog.csdn.net/v_JULY_v/article/details/7382693
参考:https://www.bilibili.com/video/BV13g41157hK?p=15&spm_id_from=333.880.my_history.page.click

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熠熠98

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值