位运算
内存中的数据,最终的存储方式都是二进制,位运算就是对整数在内存的二进制位进行操作。
按位与 &
两个整数进行按位与运算,相同二进制位的数字如果都是是,则结果为1,有一个为0,则结果为0
下面是 3 & 7 的计算过程
二进制 整数
0 1 1 3
1 1 1 7
0 1 1 3(结果)
3 & 7 = 3
按位或 |
两个整数进行按位或运算,相同二进制位的数字如果有一个为1,则结果为1,都为0,则结果为0
下面是 5 | 8 的计算过程
二进制 整数
0 1 0 1 5
1 0 0 0 8
1 1 0 1 13(结果)
5 | 8 = 13
左移 <<
二进制向左移动n位,在后面添加n个0
下面的 3 << 1 的计算过程
二进制 整数
1 1 3
1 1 0 6
3<<1 = 6
练习:一组数,内容为 3,9,19,20 ,请用一个整数来表示这四个数
var value = 0 value = value | 1<<3 value = value | 1<<9 value = value | 1<<19 value = value | 1<<20 console.log(value)
程序输出结果为:1573384
bitmap
新的实现方式
经过前面一系列的分析和位运算学习,现在我们要重新设计一个类,实现 addMember 和 isExist 方法,用更快的速度,更少的内存
- 数据范围是0~100,那么只需要4个整数就可以表示 4 * 32 个数的存在与否(如果用普通的数组方法,需要数组中有100个数来表示0-100的存在与否),创建一个大小为4的数组
- 执行addMember时,先用 member/32,确定member在数组里的索引(arr_index),然后用 member%32,确定在整数的哪个二进制位进行操作(bit_index),最后执行 bit_arr[arr_index] = bit_arr[arr_index] | 1<<bit_index
- 执行isExist时,先用 member/32,确定member在数组里的索引(arr_index),然后用 member%32,确定在整数的哪个二进制位进行操作(bit_index),最后执行 bit_arr[arr_index] & 1<<bit_index ,如果结果不为 0 ,就说明 member存在
function BitMap(size){ var bit_arr = new Array(size) for(var i=0; i<bit_arr.length; i++){ bit_arr[i] = 0 } this.addMember = function(member){ // 决定在数组中的索引 var arr_index = Math.floor(member/32) // 决定在整数的32个bit位的哪一位上 var bit_index = member%32 bit_arr[arr_index] = bit_arr[arr_index] | 1<<bit_index } this.isExist = function(member){ // 决定在数组中的索引 var arr_index = Math.floor(member/32) // 决定在整数的32个bit位的哪一位上 var bit_index = member%32 var value = bit_arr[arr_index] & 1<<bit_index if(value !== 0){ return true } return false } }
概念
这种数据结构基于位做映射,能够用很少的内存存储数据,和数组不同,它只能存储表示某个数是否存在,可以用于大数据去重,大数据排序,两个集合取交集。
BitMap在处理大数据时才有优势,而且要求数据集紧凑,如果要处理的数只有3个:1,1000,100000,那么空间利用率太低了,最大的值决定了BitMap要用多少内存。
大数据排序
有多达10亿个无序整数,已知最大值为15亿,请对这10亿个数进行排序。(BitMap做排序有个前提,就是数据是不能重复的,如果有重复的就做不成了)
传统排序算法都不可能解决这个排序问题,即便内存允许,其计算时间也是漫长的,如果使用BitMap就极为简单。
BitMap存储最大值为15亿的集合,只需要180M 的空间,空间使用完全可以接受,至于速度,存储和比较过程中的位运算速度都非常快,第一次遍历,将10亿个数都放入到BitMap中,第二次,从0到15亿进行遍历,如果在BitMap,则输出该数值,这样经过两次遍历,就可以将如此多的数据排序。
为了演示方便,只用一个很小的数组,[0, 6, 88, 7, 73, 34, 10, 99, 22],已知数组最大值是 99 ,利用BitMap排序的算法如下
var arr = [0, 6, 88, 7, 73, 34, 10, 99, 22] var sort_arr = [] var bit_map = new BitMap(4) for(var i=0; i<arr.length; i++){ bit_map.addMember(arr[i]) } for(var i=0; i<=99; i++){ if(bit_map.isExist(i)){ sort_arr.push(i) } } console.log(sort_arr)
输出结果:[ 0, 6, 7, 10, 22, 34, 73, 88, 99 ]
布隆过滤器
前面所讲的BitMap的确很厉害,可以,却有着很强的局限性,BitMap只能用来处理整数,无法处理字符串,假设让你写一个强大的爬虫,每天爬取数以亿计的网页,那么你就需要一种数据结构,能够存储你已经爬取过的 url ,这样,才不至于重复爬取。
你可能会想到使用hash函数对url进行处理,转成整数,这样,似乎又可以使用 BitMap 了,但这样还是会有问题。假设BitMap能够映射的最大值是 M ,一个url的hash值需要对M求模,这样,就会产生冲突,而且随着存储数据的增多,冲突率会越来越大。
布隆过滤器的思想非常简单,其基本思路和BitMap是一样的,可以把布隆过滤器看做是 BitMap的扩展。为了解决冲突率,布隆过滤器要求使用k个 hash函数,新增一个 key时,把 key散列成 k 个整数,然后在数组中将这 k 个整数所对应的二进制位设置为1,判断某个key是否存在时,还是使用 k 个hash函数对key进行散列,得到k个整数,如果这k个整数所对应的二进制位都是1,就说明这个key存在,否则,这个key不存在。
对于一个布隆过滤器,有两个参数需要设置,一个是预估的最多存放的数据的数量,一个是可以接受的冲突率。
hash函数
哈希函数就是将某一个不定长的对象映射为另一个定长的对象,如果你对这个概念感到困惑,你就换一个理解方法,你给hash函数传入一个字符串,它返回一个整数。为了实现一个布隆过滤器,我们需要一个好的 hash函数,计算快,冲突又少