Bloom Filter

布隆过滤器是可以用于判断一个元素是不是在一个集合里,并且相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数。但是它也是拥有一定的缺点:布隆过滤器是有一定的误识别率以及删除困难的。

假设有一个长度为m的bit类型数组,即数组只由 0 和 1 组成

再假设有k个hash函数,这些函数的输出域s都大于等于m,并且这些hash函数彼此完全独立,那么对于同一个对象(电话号码)经过k个hash函数算出来也是完全独立,可能相同 也可能不同,然后对算出来的结果都对m取余,然后在bit array上把相应的位置设置为1 ,10000条电话号码都经过相应的处理,bitmap中已经有大部分的位置都设置为1 了,

但是值得注意的是,当所有位都是1后该布隆过滤器失效

那么在检查阶段如何检查一个电话号码是否加入过呢? 用p表示11位的电话号码,要检查是否被加入过,就把p通过k个hash函数计算出k个值,然后把k个值都取余(%m)

就可以得到[0,m-1] 范围上的k个值,接下来在bitmap中看这k个位置是否都为1,如果有一个不是1 ,那么说明p不在这个集合中,但是也可能存在误判的情况,也就是 bit中的k个位置已经是1了,但p并没有加入过集合。

如果bitmap的大小m相比输入的数据的个数 n 过小,失误率会变大, 那么确定布隆过滤器的大小m和哈希函数的个数k 就是关键了

查阅资料 布隆过滤器的大小由以下公式决定

m = - n*lnp / (ln2)^2

n = 10000 , 失误率p 为 0.00001

计算出 m ~ 239852.6041666...

随机生成10000条电话号码,实现bloom filter 查找某个电话号码是否存在

在测试时发现 @Test 每次都会新建对象 ,单独运行每一个test 不能共享变量,

所以最后选择读入phones.txt文件

所以为了使误差p 在 0.01% 左右

哈希函数的个数为 k =ln2 * m/n ~ 16

seeds 可取 16的长度

代码:

package com.pys.bloomfilter;
import java.util.BitSet;
public class BloomFilter{
​
   private  int[] seeds;
   private  int size; //bitmap的大小
   private static BitSet bitmap;
   //失误率
   private MisjudgementRate rate;
   private int cnt = 0;
   private Double haveUsedRate;
​
   public BloomFilter(int dataCount){
       this(MisjudgementRate.MIDDLE,dataCount,null);
  }
​
   /**
    * 初始化布隆过滤器
    * @param rate 失误率
    * @param dataCount 数据规模 10000 条电话号码
    * @param haveUsedRate 当布隆过滤器使用率达到 haveUsedRate 后就自动清空,重新使用,防止失效
    */
   public BloomFilter(MisjudgementRate rate,int dataCount,Double haveUsedRate){
       //每一个电话号码分配 rate.seeds.length 位
       long bitSize = rate.seeds.length * dataCount;   //bitmap大小
​
       if(bitSize < 0|| bitSize >Integer.MAX_VALUE){ //数据过大
               throw new RuntimeException("数据过大");
      }
       this.rate = rate;
       seeds = this.rate.seeds;
       size = (int) bitSize;
​
       bitmap = new BitSet(size);
       this.haveUsedRate = haveUsedRate;
  }
​
   //添加入集合
   public void add(String data){
​
       for(int i = 0;i<seeds.length;i++){
           int index = hash(data,seeds[i]);
           if(!bitmap.get(index)){
               bitmap.set(index,true);//在位图中把该位置标记为1
               this.cnt++;
          }
​
      }
  }
​
   public boolean check(String data){
       for(int i = 0;i<seeds.length;i++){
           int index = hash(data,seeds[i]);
           if(!bitmap.get(index)){
               //如果该位置不是1 说明这个号码不存在
               return false;
          }
      }
       //如果都是1说明存在
       return true;
  }
​
//   //如果不存在就进行记录并返回,如果存在就返回true
//   public boolean addIfNotExist(String data){
//       int[] indexs = new int[seeds.length];
//       boolean exists = true;
//       int index;
//
//       for(int i = 0 ;i<seeds.length ;i++){
//           //记录索引
//           indexs[i] = index = hash(data,seeds[i]);
//           if(exists){
//               if(!bitmap.get(index)){
//                   //如果在位图中该位置不是1 那么该号码第一次出现
//                   exists = false;
//                   bitmap.set(index,true);//将这个位置置1
//               }
//           }else{ //不存在   将当前的位置1
//               bitmap.set(index,true);
//           }
//       }
//       return exists;
//   }
​
   //判断该过滤器的使用率是否已经到达上限,是否要清空
   public  void checkNeedClear(){
       if(haveUsedRate != null){
           if(getUseRate() >= haveUsedRate){
               this.bitmap.clear();//清空
               this.cnt = 0;
          }
      }
  }
   public  double getUseRate(){
​
       return (double) cnt / (double) size;
  }
​
   //构造hash函数
   public int hash(String data,int seed){
       char[] chars = data.toCharArray();
       int code = 0;
       for(int i = 0;i<chars.length;i++){
               code = i*code + chars[i];
      }
​
       code = code * seed % size;
       return Math.abs(code); //防止溢出
  }
   public enum MisjudgementRate {
​
       // 每一个字符串在位图中占几位 ,取质数可以降低误差率
       VERY_SMALL(new int[]{2, 3, 5, 7}),
​
       SMALL(new int[]{2, 3, 5, 7, 11, 13, 17, 19}),
​
       //16
       MIDDLE(new int[]{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53});
​
       private int[] seeds;
​
       private MisjudgementRate(int[] seeds) {
           this.seeds = seeds;
      }
​
       public int[] getSeeds() {
           return seeds;
      }
​
       public void setSeeds(int[] seeds) {
           this.seeds = seeds;
      }
  }
}
​


Test

package com.pys.bloomfilter;
​
import org.junit.Test;
​
import java.io.*;
import java.util.Map;
​
public class MainTest {
​
   final int SIZE = 10000;
   String[] phones = new String[SIZE];
   BloomFilter bloomFilter = new BloomFilter(SIZE);
   String filePath = "/Users/pingyunshangpingyunshang/Downloads/phones.txt";
​
   @Test
   public void init() {
       //生成10000个随机号码 但是因为首位是1 所有只要后10位随机即可 也就是长度为10的字符
​
       OutputStreamWriter writer = null;
​
       try {
           writer = new OutputStreamWriter(new FileOutputStream(filePath));
      } catch (FileNotFoundException e) {
           e.printStackTrace();
      }
​
       for (int i = 0; i < SIZE; i++) {
           String res = "1";
           for (int j = 0; j < 10; j++) {
               res += ((int) (Math.random() * 10)) + "";
          }
           phones[i] = res;
​
           try {
               writer.write(phones[i]);
               writer.write("\n");
          } catch (IOException e) {
               e.printStackTrace();
          }
      }
       try {
           if (writer != null) {
               writer.flush();
               writer.close();
               System.out.println("success");
          }
      } catch (IOException e) {
           e.printStackTrace();
      }
  }
​
   @Test
   public void test() {
       FileReader fileReader = null;
       BufferedReader reader = null;
       try {
           //读文件
           fileReader = new FileReader(filePath);
           reader = new BufferedReader(fileReader);
           String line;
           while ((line = reader.readLine()) != null) {
               bloomFilter.add(line);
          }
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           if (reader != null) {
               try {
                   reader.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
           //关闭资源
           if (fileReader != null) {
               try {
                   fileReader.close();
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
      }
​
       System.out.println("14930625616是否存在:"+bloomFilter.check("14930625616"));
       System.out.println("17079111577是否存在:"+bloomFilter.check("17079111577"));
       System.out.println("10636734869是否存在:"+bloomFilter.check("10636734869"));
       System.out.println("15058125173是否存在:"+bloomFilter.check("15058125173")); 
       System.out.println("17604271316是否存在:"+bloomFilter.check("17604271316"));
       System.out.println("布隆过滤器使用率"+bloomFilter.getUseRate());
  }
}
​

当变化seeds时 误判率不同

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值