随着互联网的发展,大数据应用越来越多。如何在内存有限的条件下,对超大规模数据进行效率处理,是一个值得探讨的问题。本文将以求两个文件共同元素为例,探讨一种基于布隆过滤器的高效算法。
问题描述
假设有文件A和文件B,各包含50亿个url,每个url 64字节,内存限制为4G。要求找出A和B中的共同url。
常规方法及不足
最简单的方法是将A和B分别载入内存,然后逐一比对找出交集。但每个文件达到320GB,远超过4G内存限制,无法操作。
一种改进是分批载入A和B的一部分数据,每次在内存中求交集,最后合并结果。这种方法可以控制每次内存使用,但需要对两个文件多轮遍历。当数据规模极大时,读写IO成本非常高。
再一种方法是使用外部排序算法。先分别对A和B进行排序,然后归并式地求交集。此方法需要多轮磁盘IO,在数据规模巨大时同样低效。
布隆过滤器解法
基于上述分析,需要一种能够快速判断元素是否在集合中的数据结构。布隆过滤器(Bloom Filter)可以提供这种能力。
布隆过滤器是一个空间效率很高的随机数据结构,对一个元素集合建立索引。它的特点是:
- 可以快速判断一个元素是否在过滤器表示的集合中import bloomFilter.*; // 引入布隆过滤器包 public class UrlIntersector{ public static void intersect(File A, File B){ BloomFilter filterA = new BloomFilter(A.size(), 0.01); // 初始化布隆过滤器 for(String url : A){ // 遍历文件A,加载到过滤器 filterA.add(url); } BloomFilter filterB = new BloomFilter(B.size(), 0.01); // 初始化过滤器B for(String url : B){ // 遍历文件B,加载到过滤器 filterB.add(url); } for(String url : B){ // 遍历文件B if(filterA.contains(url)){ // 判断每个url是否在过滤器A中 print(url); // 如果存在,则输出 } } } }这个示例先初始化了两个布隆过滤器,然后分别加载两个文件的url,最后判断文件B中的url是否在过滤器A中,从而找出交集。总结本文以求两个大文件交集为例,展示了如何利用布隆过滤器这个高效的数据结构解决大数据场景下的复杂问题。主要优点是:
- 只需要两轮遍历,降低了IO和计算复杂度;
- 内存占用可控,可以处理超大规模数据;
- 效率高,可实现间隔判断,不需要存储和比较全部元素。 当然布隆过滤器也存在误判率问题,需要对参数k和m进行调优,控制在可接受的范围内。 随着大数据的发展,这类空间效率高的随机算法及数据结构还有很多,比如HyperLogLog用于统计基数,Reservoir Sampling用于抽样等。这些技术可以单独使用,也可以组合应用,解决更为复杂的大数据处理问题。
- 判断不存在的元素时,可能会产生少量的误判 布隆过滤器的原理是,使用多个随机映射函数将元素映射到一个位向量中,判断元素是否在集合中时,检查它在位向量中的位置是否都为1。 具体实现上,使用m比特长度的位向量v初始化为0。还需要k个随机映射函数h1~hk,作用是将元素映射到0~m-1的整数索引上。添加元素时,将元素分别通过k个函数映射到位向量的k个位置,并将这些位置设为1。判断元素是否存在时,检查它通过k个函数映射的位置是否都是1,如果都是则判断元素存在,否则判断不存在。 优点是空间和查找效率都很高,不需要存储元素本身。缺点是有一定的误判率,但可以通过参数调整将误判控制在可接受范围。算法实现基于布隆过滤器,可以设计一个求两个文件交集的算法:
- 根据文件A的数据规模和可接受的误判率,初始化布隆过滤器A;
- 遍历文件A,将每个url输入到过滤器A中;
- 同样初始化过滤器B,遍历文件B将元素输入过滤器B;
- 遍历文件B,对每个url,判断它是否在过滤器A中,如果是,则输出这个url。 这个算法只需要两轮遍历,时间复杂度为O(n),并可以控制每次处理的数据量,保证内存不超限。相比之前的算法,效率有很大提升。 下面是Java示例代码: