C++ 数据结构之布隆过滤器

 更新时间:2020年4月25日 17:31  点击:1257

布隆过滤器

一、历史背景知识

   布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远超过一般的算法,缺点是有一定的误识别率和删除错误。而这个缺点是不可避免的。但是绝对不会出现识别错误的情况出现(即假反例False negatives,如果某个元素确实没有在该集合中,那么Bloom Filter 是不会报告该元素存在集合中的,所以不会漏报)

在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上;在网络爬虫里,一个网址是否被访问过等等。最直接的方法就是将集合中全部的元素存在计算机中,遇到一个新 元素时,将它和集合中的元素直接比较即可。一般来讲,计算机中的集合是用哈希表(hash table)来存储的。它的好处是快速准确,缺点是费存储空间。当集合比较小时,这个问题不显著,但是当集合巨大时,哈希表存储效率低的问题就显现出来 了。

比如说,一个象 Yahoo,Hotmail 和 Gmai 那样的公众电子邮件(email)提供商,总是需要过滤来自发送垃圾邮件的人(spamer)的垃圾邮件。一个办法就是记录下那些发垃圾邮件的 email 地址。由于那些发送者不停地在注册新的地址,全世界少说也有几十亿个发垃圾邮件的地址,将他们都存起来则需要大量的网络服务器。如果用哈希表,每存储一亿 个 email 地址, 就需要 1.6GB 的内存(用哈希表实现的具体办法是将每一个 email 地址对应成一个八字节的信息指纹,然后将这些信息指纹存入哈希表,由于哈希表的存储效率一般只有 50%,因此一个 email 地址需要占用十六个字节。一亿个地址大约要 1.6GB, 即十六亿字节的内存)。因此存贮几十亿个邮件地址可能需要上百 GB 的内存。除非是超级计算机,一般服务器是无法存储的[2]。

二、布隆过滤器原理以及优缺点

如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(哈希表,Hash table)等数据结构都是这种思想。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也会越来越慢。

Bloom Filter 是一种空间效率很高的随机数据结构,Bloom Filter 可以看做是对bit-map的扩展,它的原理是:

当一个元素被加入集合中时,通过K个hash函数将这个元素映射成一个位阵列(Bit array)中的K个点,将它们置成1. 检索时,我们只需要看这些点是不是都是1就能(大约)知道集合中有没有它:

如果这些点中有任何一个0,则被检索元素一定不在;

如果都是1,则被检索元素很可能在。

优点:

它的优点是空间效率和查询时间都远远超过一般的算法,布隆过滤器存储空间和插入\查询时间都是O(K),另外,散列函数相互之间没有关系,方便硬件并行实现,布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。

缺点:

1、布隆过滤器的缺点和优点同样明显。误算率是其中之一。随着存入元素的增加,误算率随之增加。但是元素数量太少,则使用散列就可以了。

2、一般情况下不能从布隆过滤器中删除元素,我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加1,这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非这么简单。首先我们必须保证删除的元素的确存在布隆过滤器里面,另外计数器回绕也会造成问题。

三、example

Google Chrome浏览器使用Bloom filter识别恶意链接(能用较小的存储空间表示较大的数据集合,简单想就是把 每一个URL都可以映射成bit)的多,并且误判率在万分之一以下。

C++实现

bit_set.h

#pragma once 
#include<iostream> 
using namespace std; 
#include<vector> 
 
class Bitset 
{ 
public: 
  Bitset(size_t value) 
  { 
    _a.resize((value >> 5) + 1, 0); 
  } 
  bool set(size_t num) 
  { 
    size_t index = num>>5; 
    size_t pos = num % 32; 
    if (_a[index] & (1 << (31 - pos))) 
    { 
      return false; 
    } 
    else 
    { 
      _a[index] |= (1 << (31 - pos)); 
      _size++; 
      return true; 
    } 
     
  } 
  bool Reset(size_t num) 
  { 
    size_t index = num >> 5; 
    size_t pos = num % 32; 
    if (Text(num)) 
    { 
      _a[index] &= ~(1 << (31 - pos)); 
      _size--; 
      return true; 
    } 
    else 
    { 
      return false; 
    } 
  } 
  bool Text(size_t num) 
  { 
    size_t index = num >> 5; 
    size_t pos = num % 32; 
    return _a[index] & (1 << (31-pos)); 
  } 
private: 
  vector<int> _a; 
  size_t _size; 
}; 

Hash.h

#pragma once 
template<class T> //各类哈希字符串转换函数  
size_t BKDRHash(const char *str) 
{ 
  register size_t hash = 0; 
  while (size_t ch = (size_t)*str++) 
  { 
    hash = hash * 131 + ch; 
  } 
  return hash; 
} 
 
template<class T> 
size_t SDBMHash(const char *str) 
{ 
  register size_t hash = 0; 
  while (size_t ch = (size_t)*str++) 
  { 
    hash = 65599 * hash + ch; 
  } 
  return hash; 
} 
 
template<class T> 
size_t RSHash(const char * str) 
{ 
  size_t hash = 0; 
  size_t magic = 63689; 
  while (size_t ch = (size_t)*str++) 
  { 
    hash = hash * magic + ch; 
    magic *= 378551; 
  } 
  return hash; 
} 
 
 
template<class T> 
size_t APHash(const char *str) 
{ 
  register size_t hash = 0; 
  size_t ch; 
  for (long i = 0; ch = (size_t)*str++; i++) 
  { 
    if ((i & 1) == 0) 
    { 
      hash ^= ((hash << 7) ^ ch ^ (hash >> 3)); 
    } 
    else 
    { 
      hash ^= (~((hash << 11) ^ ch ^ (hash >> 5))); 
    } 
  } 
  return hash; 
} 
 
 
template<class T> 
size_t JSHash(const char* str) 
{ 
  if (!*str) 
  { 
    return 0; 
  } 
  size_t hash = 1315423911; 
  while (size_t ch = (size_t)*str++) 
  { 
    hash ^= ((hash << 5) + ch + (hash >> 2)); 
  } 
  return hash; 
} 

Bloom_Filter.h

#pragma once 
 
#include"bite_set.h" 
#include"Hash.h" 
#include<string> 
 
template<class T> 
struct __HashFunk1 
{ 
  size_t operator()(const T& key) 
  { 
    return BKDRHash<T>(key.c_str()); 
  } 
}; 
 
template<class T> 
struct __HashFunk2 
{ 
  size_t operator()(const T& key) 
  { 
    return SDBMHash<T>(key.c_str()); 
  } 
};  
 
template<class T> 
struct __HashFunk3 
{ 
  size_t operator()(const T& key) 
  { 
    return RSHash<T>(key.c_str()); 
  } 
}; 
 
template<class T> 
struct __HashFunk4 
{ 
  size_t operator()(const T& key) 
  { 
    return APHash<T>(key.c_str()); 
  } 
}; 
 
template<class T> 
struct __HashFunk5 
{ 
  size_t operator()(const T& key) 
  { 
    return JSHash<T>(key.c_str()); 
  } 
}; 
 
 
template<class K = string, 
class HashFunk1 = __HashFunk1<K>, 
class HashFunk2 = __HashFunk2<K>, 
class HashFunk3 = __HashFunk3<K>, 
class HashFunk4 = __HashFunk4<K>, 
class HashFunk5 = __HashFunk5<K>> 
 
class BoolFilter 
{ 
public: 
  BoolFilter(size_t n) 
    :_a(n * 10) 
    , _range(n * 10) 
  {} 
 
  void set(const K& key) 
  { 
    _a.set(HashFunk1()(key) % _range); 
    _a.set(HashFunk2()(key) % _range); 
    _a.set(HashFunk3()(key) % _range); 
    _a.set(HashFunk4()(key) % _range); 
    _a.set(HashFunk5()(key) % _range); 
  } 
 
  bool Text(const K& key) 
  { 
    if (!_a.Text(HashFunk1()(key)% _range)) 
      return false; 
    if (!_a.Text(HashFunk2()(key) % _range)) 
      return false; 
    if (!_a.Text(HashFunk3()(key) % _range)) 
      return false; 
    if (!_a.Text(HashFunk4()(key) % _range)) 
      return false; 
    if (!_a.Text(HashFunk5()(key) % _range)) 
      return false; 
    return true; 
  } 
private: 
  Bitset _a; 
  size_t _range; 
}; 


[!--infotagslink--]

相关文章

  • C++ STL标准库std::vector的使用详解

    vector是表示可以改变大小的数组的序列容器,本文主要介绍了C++STL标准库std::vector的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-06
  • C++中取余运算的实现

    这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ string常用截取字符串方法

    这篇文章主要介绍了C++ string常用截取字符串方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • C++中四种加密算法之AES源代码

    本篇文章主要介绍了C++中四种加密算法之AES源代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...2020-04-25
  • C++ 整数拆分方法详解

    整数拆分,指把一个整数分解成若干个整数的和。本文重点给大家介绍C++ 整数拆分方法详解,非常不错,感兴趣的朋友一起学习吧...2020-04-25
  • C++中 Sort函数详细解析

    这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
  • C++万能库头文件在vs中的安装步骤(图文)

    这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ bitset用法

    这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • 浅谈C++中的string 类型占几个字节

    本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节...2020-04-25
  • C++ Eigen库计算矩阵特征值及特征向量

    这篇文章主要为大家详细介绍了C++ Eigen库计算矩阵特征值及特征向量,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-04-25
  • C++ pair的用法实例详解

    这篇文章主要介绍了C++ pair的用法实例详解的相关资料,需要的朋友可以参考下...2020-04-25
  • VSCode C++多文件编译的简单使用方法

    这篇文章主要介绍了VSCode C++多文件编译的简单使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-29
  • C++中的循环引用

    虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题。下面通过实例代码给大家介绍c++中的循环引用,一起看看吧...2020-04-25
  • C++随机点名生成器实例代码(老师们的福音!)

    这篇文章主要给大家介绍了关于C++随机点名生成器的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++如何删除map容器中指定值的元素详解

    map容器是C++ STL中的重要一员,删除map容器中value为指定元素的问题是我们经常与遇到的一个问题,下面这篇文章主要给大家介绍了关于利用C++如何删除map容器中指定值的元素的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。...2020-04-25
  • C++ 约瑟夫环问题案例详解

    这篇文章主要介绍了C++ 约瑟夫环问题案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...2021-08-15
  • C++中cin的用法详细

    这篇文章主要介绍了C++中cin的用法详细,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • 基于C++中常见编译错误的总结详解

    本篇文章是对C++中的常见编译错误进行了详细的分析介绍,需要的朋友参考下...2020-04-25
  • c++优先队列(priority_queue)用法详解

    这篇文章主要介绍了c++优先队列(priority_queue)用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25