文档章节

算法分析:使用布隆过滤器(Bloom Filter)进行大数据量排序

苗哥
 苗哥
发布于 2014/03/20 12:24
字数 2871
阅读 2931
收藏 14
      题目大意:移动公司需要对已经发放的所有139段的号码进行统计排序,已经发放的139号码段的文件都存放在一个文本文件中(原题是放在两个文件中),一个号码一行,现在需要将文件里的所有号码进行排序,并写入到一个新的文件中;号码可能会有很多,最多可能有一亿个不同的号码(所有的139段号码),存入文本文件中大概要占1.2G的空间;JVM最大的内存在300以内,程序要考虑程序的可执行性及效率;只能使用Java标准库,不得使用第三方工具。
      这是个典型的大数据量的排序算法问题,首先要考虑空间问题,一下把.2G的数据读入内存是不太可能的,就算把壹亿条数据都转换成INT类型存储也要占接近400M的空间。当时做个题目我并没有想太多的执行效率问题,主要就考虑了空间,而且习惯性的想到合并排序,基本思想是原文件分割成若干个小文件并排序,再将排序好的小文件合并得到最后结果,算法大概如下:
1、顺序读取存放号码文件的中所有号码,并取139之后的八位转换为int类型;每读取号码数满一百万个(这个数据可配置)将已经读取的号码排序并存入新建的临时文件。
2、将所有生成的号码有序的临时文件合并存入结果文件。
这个算法虽然解决了空间问题,但是运行效率极低,由于IO读写操作太多,加上步骤1中的排序的算法(快速排序)本来效率就不高(对于电话排序这种特殊情况来说),导致1亿条数据排序运行3个小时才有结果。
如何能够减少排序的时间呢?首当其冲的是减少IO操作,另外如果能够有更加好排序算法也行。前天无聊再看这个题目时突然想到大三时看《编程珠玑》时上面也有个问题的需求这个这个题目差不多,记得好像使用是位向量(实际上就是一个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){
	//取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上),看来效果还是很明显的。

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

1、只能用于整数的排序,或者可以准确映射到正整数(对象不同对应的正整数也不相同)的数据的排序。
2、不能处理重复的数据,重复的数据排序后只有一条,如果有这种需求,可以在这个算法的基础上修改,给出现次数大于1的数据添加個计数器即可,然后存入Map中。
3、对于数据量极其大的数据处理可能还是比较占用空间,这种情况可配合多通道排序算法解决。
这个算法的思想源于《 编程珠玑》中的 布隆过滤器(Bloom Filter),有兴趣的同学可以读读那本书,非常不错! http://book.douban.com/subject/1230206/

布隆过滤器(Bloom Filter)

在日常生活中,包括在设计计算机软件时,我们经常要判断一个元素是否在一个集合中。比如在字处理软件中,需要检查一个英语单词是否拼写正确(也就是要判断它 是否在已知的字典中);在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上;在网络爬虫里,一个网址是否被访问过等等。最直接的方法就是将集合中全部的元素存在计算机中,遇到一个新 元素时,将它和集合中的元素直接比较即可。一般来讲,计算机中的集合是用哈希表(hash table)来存储的。它的好处是快速准确,缺点是费存储空间。当集合比较小时,这个问题不显著,但是当集合巨大时,哈希表存储效率低的问题就显现出来了。比如说,壹個像 Yahoo,Hotmail 和 Gmai 那样的公众电子邮件(email)提供商,总是需要过滤来自发送垃圾邮件的人(spamer)的垃圾邮件。一个办法就是记录下那些发垃圾邮件的 email 地址。由于那些发送者不停地在注册新的地址,全世界少说也有几十亿个发垃圾邮件的地址,将他们都存起来则需要大量的网络服务器。如果用哈希表,每存储一亿 个 email 地址, 就需要 1.6GB 的内存(用哈希表实现的具体办法是将每一个 email 地址对应成一个八字节的信息指纹, 然后将这些信息指纹存入哈希表,由于哈希表的存储效率一般只有 50%,因此一个 email 地址需要占用十六个字节。一亿个地址大约要 1.6GB, 即十六亿字节的内存)。因此存贮几十亿个邮件地址可能需要上百 GB 的内存。除非是超级计算机,一般服务器是无法存储的。

今天,我们介绍一种称作布隆过滤器的数学工具,它只需要哈希表 1/8 到 1/4 的大小就能解决同样的问题。

布隆过滤器是由巴顿*布隆于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 对应的八个二进制一定是一。这样在遇到任何在黑名单中的电子邮件地址,我们都能准确地发现。
  布隆过滤器决不会漏掉任何一个在黑名单中的可疑地址。但是,它有一条不足之处。也就是它有极小的可能将一个不在黑名单中的电子邮件地址判定为在黑名单中,因为有可能某个合理使用的邮件地址正巧对应个八个都被设置成一的二进制位。好在这种可能性很小。我们把它称为误识概率。在上面的例子中,误识概率在万分之一以下。
布隆过滤器的好处在于快速,省空间。但是有一定的误识别率。常见的补救办法是在建立一个小的白名单,存储那些可能别误判的邮件地址。

本文转载自:http://pisces-java.iteye.com/blog/766745

共有 人打赏支持
苗哥

苗哥

粉丝 243
博文 110
码字总数 130745
作品 0
上海
后端工程师
加载中

评论(3)

Timco
Timco

引用来自“苗哥”的评论

引用来自“Timco”的评论

最近也学习了一下布隆算法,容易理解,但是多个hash函数不知道如何设计

我也是最近开始研究这個算法的,共同学习,哈哈...

要是最后对于k个hash算法的设计有什么见解和思路的话,记得博客分享一下啊...
苗哥
苗哥

引用来自“Timco”的评论

最近也学习了一下布隆算法,容易理解,但是多个hash函数不知道如何设计

我也是最近开始研究这個算法的,共同学习,哈哈...
Timco
Timco
最近也学习了一下布隆算法,容易理解,但是多个hash函数不知道如何设计
BitSet和布隆过滤器(Bloom Filter)

布隆过滤器 Bloom Filter 是由Howard Bloom 在 1970 年提出的二进制向量数据结构,它具有很好的空间和时间效率,被用来检测一个元素是不是集合中的一个成员。如果检测结果为是,该元素不一定...

凯文加内特
2016/01/15
187
0
海量数据判重——布隆过滤器(Bloom filter)与Bitmap对比

前言 之前写过一篇Bitmap在海量整数排序中应用的博客,在看过布隆过滤器之后,感觉两个有些相似,但是又有区别,在查阅了很多资料之后,这里决定稍作总结。 关于布隆过滤器(Bloom filter)的...

tick_tock97
2017/12/01
0
0
从还有一个角度看大数据量处理利器:布隆过滤器

思路:从简单的排序谈到BitMap算法。再谈到数据去重问题,谈到大数据量处理利器:布隆过滤器。 情景1:对无反复的数据进行排序 @给定数据(2,4。1,12。9,7,6)怎样对它排序? 方法1:主要...

技术mix呢
2017/12/07
0
0
如何实现大数据集查询?Bloom Filter或许是你想要的

1、什么情况下需要布隆过滤器? 先来看几个比较常见的例子 字处理软件中,需要检查一个英语单词是否拼写正确 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上 在网络爬虫里,一个网址是否被访...

流川枫AI
2017/06/18
0
0
Bloom filter在分布式环境中的应用

Bloom filter在分布式环境中的应用 未命名2017-05-0427 阅读 filter技术分布式 概述 布隆过滤器是一个应用非常广泛的概率型数据结构,一般用于判断一个元素是否存在一个集合中,比如在字处理...

未命名
2017/05/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

移除或自定义 WordPress 仪表盘欢迎面板

第一次登录 WordPress 后台仪表盘页面,默认都会显示 WordPress 的欢迎面板: 如果我们要移除这个面板,在主题的 functions.php 中添加下面的代码即可: 12 //移除 WordPress 仪表盘欢迎面...

james_laughing
7分钟前
0
0
HashMap实现原理及源码分析

HashMap实现原理及源码分析   哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,...

DemonsI
11分钟前
0
0
eggjs学习笔记

快速初始化 生成项目(要求最低的node版本8.x) npm i egg-init -gegg-init egg-example --type=simplecd egg-examplenpm i 启动项目 npm run dev 配置 环境配置会覆盖默认配置 config...

别人说我名字很长
14分钟前
1
0
Winform Timer控件时间间隔

sender as System.Timers.Timer).Interval = 23 * 60 * 60 * 1000.0;//将时间间隔改为23小时,23小时后重新发生timer_Elapsed事件。 //60000:时间间隔1分钟,300000:时间间隔5分钟,600000:...

笑丶笑
14分钟前
0
0
在win10系统下怎样快速切换任务视图

切换窗口:Alt + Tab 任务视图:Win + Tab (松开键盘界面不会消失) 切换任务视图:Win + Ctrl +左/右 创建新的虚拟桌面:Win + Ctrl + D 关闭当前虚拟桌面:Win + Ctrl + F4...

SummerGao
18分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部