实现位映射与布隆过滤器

1. 位映射

1.1为什么要用位映射

少量的数据可以存储在arry或者link中,需要查找的时候,把所有的数据都读到内存里,然后用for去遍历。但是有大量的数据的话,内存就放不下了。比如,在10亿个不重复的int中查找一个数。

1.2初步构思

把int映射到bit上,由于二者大小相差32倍,所以可以节省大量空间。

在这里插入图片描述

1.3有个问题需要解决

因为计算机很多底层,例如IO是以byte位单位传输数据,所以java以及很多编程语言并没有bit[ ]。所以需要用byte[ ]代劳一下,此处以一个byte数组存储1-8的int为例。说明byte[ ]是如何代替bit[ ]存储数组的。
在这里插入图片描述

1.4代码

  1. new一个byte[2^29]. int有2 ^32种可能,即需要一个bit[2 ^32]。一个byte=8个bit,所以除以2 ^3
  2. 计算int的对应的bit[ ] 上的位置,也是int的顺序位置。int的范围为[-2^31 ,2^31], 但是bit[ ]索引的范围应为(0, 2^ 32)。所以要将int + 2^31。(是+不是*)
  3. 首先求出int在哪个byte中。因为bit和byte是8倍的关系,所以将1.中的索引/8。
  4. 最后求出int在byte内部的索引。1.中的索引/8求余数。

位运算技巧:
1向左推n位,就是2的n次方
想取第n位,就向右推n-1位,再&1
想在num的第n位加入1,就将1向左推n-1位,再 | num

public class BinMap {

    byte[] bytes=new byte[1<<29];

    public void add(int num){

        /*这里l指的是在底层新建一个long类型的值。
        num需要+2^31,这里使用位运算来达到相同的效果
        这样的话,int中最小的-2^31的索引为0,以此类推*/
        long bitIndex=num+(1l<<31);
        int byteInd= (int) (bitIndex/8);
        int inInd=(int) (bitIndex%8);
        bytes[byteInd]= (byte) (bytes[byteInd] | (1 << (inInd-1)));
    }

    public boolean retrieve(int num){

        long bitIndex=num+(1l<<31);
        int byteInd=(int)(bitIndex/8);
        int inInd=(int)(bitIndex%8);
        if (((bytes[byteInd] >> (inInd-1))&1)==1){
            return true;
        }else {
            return false;
        }

    }
}

2. BloomFilter

2.1布隆过滤器原理

如下图所示,将一个要存储的数据,使用n个不同的hash函数计算n个hashCode,然后储存在 1.位映射 中的byte中。
为什么要用多个哈希函数呢? 虽然多个哈希函数会带来哈希冲突,会造成误差。但同时,这种误差会缩小byte[ ]所需要的空间,毕竟只是个过滤器,并不要求有多精确。所以在可容忍误差下减小所占用的空间是可行的。
在这里插入图片描述

2.2误差问题

上文中能很明显的看到一个问题,存入的数据越多,bloomFilter的误差越大。当然,也可以通过加长byte[ ]来减小误差。换言之,布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。
具体的假阳性率,从网上抄了一个公式:
在这里插入图片描述
k: hash函数的个数;n: 已存入数据的个数;m: byte[ ]的长度;p: 误报率
再实际业务中,可以使用如下公式来确定k and m:
在这里插入图片描述

2.3使用场景

bloomfilter有如下特点:

  • 上文提到的误差特点。
  • 多个hash函数可以使用多线程来提高效率。
  • 增加和查询操作的复杂度都是常数,能实现以空间换时间
  • 过滤器的byte[ ]只储存了0,1,而没储存具体的值,所以安全性更好
  • 删除很难。比如"123"映射在byte[1],[2],[3] ,“234"映射在byte[2],[3],[4] 。如果删除了"123”, 那么"234" 会很尴尬~

该方法并不适合在严密的计算中使用,但是由于其假阳性率高,假阴性率低的特点,可以对请求进行初步的过滤(所以叫做过滤器),以减小真正查询操作的压力。例如:

  • 防止缓存穿透,即先过滤查询请求,如通过,再提交给数据库进行查询
  • 邮箱啥的黑名单过滤(所以邮箱黑名单屏蔽总那么不靠谱)
  • 去重,比如爬给定网址的时候对已经爬取过的 URL 去重,避免给用户推送重复内容

2.4代码

  1. 弄若干个不同的hash算法
  2. 计算hashCode
  3. 存入数组
public class BloomFilter {

    BinMap map=new BinMap();

    public void add(int num){
        int[] hash=hash(num);
        for (int i=0;i<hash.length;i++) {
            map.add(hash[i]);
        }
    }

    public boolean retrieve(int num){
        boolean f=true;
        int[] hash=hash(num);
        for (int i=0;i<hash.length;i++) {
            f=f&map.retrieve(hash[i]);
        }
        return f;
    }

    public int[] hash(int num){
        int[] ints=new int[3];
        ints[0]=Math.abs(13 * (33554432 - 1) & num);
        ints[1]=Math.abs(46 * (33554432 - 1) & num);
        ints[2]=Math.abs(71 * (33554432 - 1) & num);
        return ints;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值