首页 > 编程语言 >位图&布隆过滤器剖析 #C++

位图&布隆过滤器剖析 #C++

时间:2023-09-30 13:05:02浏览次数:54  
标签:哈希 布隆 C++ str 过滤器 bit 位图

位图

位图概述

位图(bit set)中存储位(bit),每个元素只有两个可能值,1/0 或者 true/false。与bool数组相比,位图的空间开销更小,每个元素占据 1bit 空间,是C++最小内置类型char的八分之一。

位图是哈希思想衍生出的容器,在完成哈希表判断元素存在功能的同时,极大地节省了所需的内存空间。位图的每个位都可以被单独访问,例如给定一个位图bitSet, 则 bitSet[3] 访问的是该位图的第4个位。

位图实现

位图以vector作为底层存储容器,因为在几乎所有的C++容器中不存在空间是 1bit 的数据类型,所以vector在宏观上存储的其实是 int 类型或者 char 类型,在进行操作时,位图内部会通过计算找到对应的 bit 位置,并对其进行操作。位图的大小(容量)是确定的,通过用户由非类型模板参数指定。

template<size_t N>
class bit_map
{
private:
  typedef bit_map<N> self;

public:
  bit_map()
  {
    //通过所需bit位数计算实际需要的内存空间,向上取整
    _bitMap.resize(N / 32 + 1);
  }

  self& set(size_t n)
  {
    int i = n / 32; //目标位处于第i个整型的第j个bit位
    int j = n % 32;
    _bitMap[i] |= (1 << j); //将目标位 置1
    return *this;
  }

  self& reset(size_t n)
  {
    int i = n / 32;
    int j = n % 32;
    _bitMap[i] &= (~(1 << j)); //将目标位 置0
    return *this; 
  }

  self& flip(size_t n) //翻转目标位
  {
    if (test(n)) {
      reset(n);
    }
    else {
      set(n);
    }
    return *this;
  }

  bool test(size_t n) //检查目标位的状态
  {
    int i = n / 32;
    int j = n % 32;
    return (_bitMap[i] & (1 << j));
  }

private:
  vector<int> _bitMap; //此处以vector<int>作为底层存储容器
};

虽然在大端机器和小端机器中,数据的存储方式有所差异,但是在位图的实现中不必考虑这些细节,机器会在底层操作时自行处理。

位图&布隆过滤器剖析 #C++_位图

位图应用

位图用于处理海量数据相关的问题,极简的内存开销是选择它的理由。当以所有32位整型作为位图有效数据的范围(0~4294967295)时,位图所需要的内存空间为 500MB 左右。

位图主要解决下面的问题:

  • 海量数据中,判断整型数据是否存在。用一个位图对所有数据进行映射即可。在linux2.6内核的进程调度中,位图用于找到不为空的优先级队列。
  • 海量数据中,找出指定出现次数(1次、2次、多次)的整型数据。用两个位图,以两个位图中相同位置的状态 00, 01, 10, 11 可以标识数据出现的次数。
  • 海量数据的交集问题。将两个数据集合分别映射到两个位图,对应位进行与运算即可。

位图的局限性

除了不能获取元素本身,位图还有另一大局限性。位图只适用于解决与整型相关的问题,这是因为整型可以轻松地映射到位图中的某个位置,并且不同的值不会造成映射冲突。当目标数据是其他类型,例如字符串、浮点型时,会造成存在误判(假阳性)。例如,当字符串 str1 通过哈希函数计算出的索引为 n 且在位图中存在,且 str2 通过哈希函数映射出的索引也为 n 且在位图中不存在时,就会对 str2 的存在造成误判。这种误判不可避免,但是可以通过布隆过滤器尽量减少误判的概率。

位图&布隆过滤器剖析 #C++_误判率_02

布隆过滤器

布隆过滤器概述

布隆过滤器(bloom filter)可以尽量减少字符串映射到位图后的假阳率。布隆过滤器的做法是,将同一个字符串,通过多个不同的哈希函数映射到多个位置,当进行test时,检查对应的多个位置,如果某一个位置为0,则说明字符串一定不存在;如果都为1,则存在较低的假阳概率。通过控制bit vector的大小和哈希函数的个数,可以将假阳率控制在 1% 以下。

在 jason davies 主页一篇关于 bloom filter 的文章中,可以支持读者与一个可视化的布隆过滤器进行互动:Bloom Filters (jasondavies.com)

位图&布隆过滤器剖析 #C++_误判率_03

传统的布隆过滤器不支持删除,这是因为某些位可能与字符串存在一对多的关系,一旦进行位的修改,会对其他数据造成影响。布隆过滤器不能获取元素本身。

布隆过滤器实现

传统的布隆过滤器支持两个主要接口: settest。这里使用三个哈希函数进行映射,进行 set 和 test 时,分别对 hashing 出的三个位置进行操作或检查。

///////////////////////////////////////////////////////
//选择三个哈希函数
struct BKDRHash
{
  size_t operator()(const string& str)
  {
    size_t hash = 0;
    for(auto& ch : str)
    {
      hash = hash * 131 + ch;
    }
    return hash;
  }
};

struct APHash
{
  size_t operator()(const string& str)
  {
    size_t hash = 0;
    for(size_t i = 0; i < str.size(); ++i)
    {
      char ch = str[i];
      if(ch & 1)
      {
        hash ^= (~((hash << 11) ^ (str[i++]) ^ (hash >> 5)));
      }
      else 
      {
        hash ^= ((hash << 7) ^ (str[i++]) ^ (hash >> 3));
      }
    }
    return hash;
  }
};

struct DJBHash
{
  size_t operator()(const string& str)
  {
    size_t hash = 0;
    for(size_t i = 0; i < str.size(); ++i)
    {
      hash += (hash << 5) + (str[i++]);
    }
    return (hash & 0x7FFFFFFF);
  }
};
//////////////////////////////////////////////////////////

template<size_t N, class hash1 = BKDRHash, 
class hash2 = APHash, class hash3 = DJBHash>
class bloom_filter
{
private:
  typedef bloom_filter<N> Self;

public:
  Self& set(const string& str)
  {
    //对三个位置分别set
    _bitSet.set((hash1()(str)) % N);
    _bitSet.set((hash2()(str)) % N);
    _bitSet.set((hash3()(str)) % N);
  }

  bool test(const string& str)
  {
    //同时检查三个位置
    //三个位置同时为1时,数据可能存在
    //三个位置中某一个位置为0,数据一定不存在
    return _bitSet.test(hash1()(str) % N) && 
      _bitSet.test(hash2()(str) % N) &&
      _bitSet.test(hash3()(str) % N);
  }

private:
  bitset<N> _bitSet;
};

布隆过滤器应用

布隆过滤器适用于一些不需要精确判断的场景。在实际使用中,布隆过滤器往往处在用户与数据库(或其他存储设备)之间,布隆过滤器可以对数据的存在性进行初步判断,如果判定为不存在,则直接返回结果给用户;如果判定为存在,则存在假阳率,进一步去存储设备中确认。

位图&布隆过滤器剖析 #C++_位图_04

在这种场景中,布隆过滤器真正发挥了它 “过滤” 的功能,大大降低了数据库的负载,提高了判断效率。

误判率与相关因素的大致分析

bit vector的大小是影响布隆过滤器误判率的关键因素。如果bit vector太小,则过滤器会很快被填满,对于每一个输入,都会返回假阳性的结果。所以bit vector的大小是必须要考虑的,较大的bit vector会产生较少的误判,较小的bit vector会产生较多的误判。此外,哈希函数的选择与数量也是一个重要因素,选择的哈希函数越多,过滤器性能相对越低,过滤器越容易被填满;但是如果哈希函数较少,可能会造成更多的误判。

下面的图片描述了bit vector的大小(m)、哈希函数(k)的数量与误判率(p)的关系:

位图&布隆过滤器剖析 #C++_假阳性_05

(图片出自 Probabilistic Data structures: Bloom filter by @me_shaon )

可见,当bit vector的大小为100M,哈希函数的数量在3个及以上时,假阳率会大幅下降至 1% 以下。根据实际场景,调整误判率至所需要的程度是个较好的选择。

标签:哈希,布隆,C++,str,过滤器,bit,位图
From: https://blog.51cto.com/158SHI/7661836

相关文章

  • 结对项目,用C++实现的四则运算
    软件工程计科一班陈倚星-3119000414,甫尔达吾斯.吐拉江-3119000416作业要求与班上同学组队完成项目作业目的提高合作与团队意识GitHub链接https://github.com/xingch123456789/my_appPSP表格PSP2.1PersonalSoftwareProcessStages预估耗时(分钟......
  • C++的extern关键字在HotSpot VM中的重要应用
    extern关键字有两个用处:(1)extern在C/C++语言中表示函数和全局变量作用范围(可见性)的关键字,这个关键字会告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。(2)在C++中引用C语言中的函数和变量,在包含C语言头文件时,需要使用extern"C"来处理。 1、extern表示函数和变量作......
  • mlpack is an intuitive, fast, and flexible header-only C++ machine learning libr
    https://github.com/mlpack/mlpack README.md afast,header-onlymachinelearninglibraryHome | Documentation | Community | Help | IRCChat   Download: currentstableversion(4.2.1)mlpack isanintuitive,fast,andflexibleheader-......
  • C++11 多线程< 一>、介绍
    1#include<iostream>2#include<thread>34voidfun1()5{6std::cout<<"fuck"<<std::endl;7}89intmain()//主线程10{11std::threadt1(fun1);//t1线程12//t1.join();//主线程和t1互不干扰,......
  • QML中使用C++对象
    QML中使用C++对象原文链接:(60条消息)QtQuick之QML与C++混合编程详解_qmlc++_foruok的博客-CSDN博客QtQuick技术的引入,使得你能够快速构建UI,具有动画、各种绚丽效果的UI都不在话下。但它不是万能的,也有很多局限性,原来Qt的一些技术,比如低阶的网络编程如QTcpSoc......
  • C++友元和运算符重载
    友元classbuiding{friendvoidGoodboy(buiding*bui);public:intm_age;private:intm_size;};//全局函数voidGoodboy(buiding*bui){cout<<bui->m_age<<endl;//可以调用public中的m_agecout<<bui->m_size<<endl;//m_size调用需要声明友元}私有......
  • 结对项目:用C++实现四则运算
    软工作业3:自动生成小学四则运算题目的命令行程序这个作业属于哪个课程计科21级12班这个作业要求在哪里结对项目这个作业的目标熟悉合作开发流程项目Github点击这里团队成员姓名学号石云欣3221004809沈纪康3121004750PSP表PSP2.......
  • C++内存模型
    目录C++内存模型存储持续性内存分配位置链接性作用域对于函数C++内存模型存储持续性C++存储持续性有以下类别:自动存储持续性:在函数定义中声明的变量(包括函数参数)。静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量。线程存储持续性(C++11):使用关键字thread......
  • JAVA代码使用JNI的方式调用C/C++动态库
    JNI(javanativeinterface),通过JNI的方式调用动态库步骤比较麻烦,不用额外引入依赖,对java项目工程依赖侵入为0,类中含有native描述的方法都会与动态库去一一映射,能通过System.load()函数去加载动态库,这种方式主要使用的场景是java写好类(一般不是接口),让C或者C++去实现......
  • C++原子变量atomic详解
    b站视频文章1C++中原子变量确保共享变量的操作在执行时不会被其他线程的操作干扰。无法复制/移动对象。is_lock_free函数:atomic对象是否支持无锁操作(什么意思?如果atomic对象需要锁,那设为atomic对象的意义是什么?)std::atomic_flag是C++中的一个原子布尔类型,它用于实现原子锁......