日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

海量数据处理(一) :位图与布隆过滤器的概念以及实现

發布時間:2024/4/11 编程问答 57 豆豆
生活随笔 收集整理的這篇文章主要介紹了 海量数据处理(一) :位图与布隆过滤器的概念以及实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

  • 位圖
    • 位圖概念
    • 位圖的應用
    • 位圖的實現思路
      • set
      • reset
      • test
      • 完整代碼
  • 布隆過濾器
    • 布隆過濾器概念
    • 布隆過濾器的優缺點
      • 優點
      • 缺點
    • 布隆過濾器的實現思路
      • 哈希沖突的問題
      • 如何選擇哈希函數個數和布隆過濾器長度
      • 插入
      • 查找
      • 刪除
      • 完整代碼


位圖

位圖概念


位圖其實就是哈希的變形,他同樣通過映射來處理數據,只不過位圖本身并不存儲數據,而是存儲標記。通過一個比特位來標記這個數據是否存在,1代表存在,0代表不存在。

位圖通常情況下用在數據量龐大,且數據不重復的情景下判斷某個數據是否存在。

例如下面這道十分經典的題目

給40億個不重復的無符號整數,沒排過序。給一個無符號整數,如何快速判斷一個數是否在這40億個數
中。

關于這道題目,解法其實有很多。
1.快速排序后二分搜索。(內存可能不夠,要16G內存)
2.位圖處理,(40億無符號整數用位圖標記只需要512M的內存)

位圖的解法差不多是這道題的最優解,只需要將所有數據讀入后將對應位置置1,然后再查找那個數據所儲的位置是否為1即可。


位圖的應用

  • 快速查找某個數據是否在一個集合中
  • 排序
  • 求兩個集合的交集、并集等
  • 操作系統中磁盤塊標記

  • 位圖的實現思路

    為了方便實現,位圖的底層可以使用一個vector而開空間并不根據數據的個數來開,而是根據數據的范圍來開(如果開的空間不夠,可能有位置無法映射到)并且一個整型具有32個字節,所以如果我們要存N個數據,就只需要開N / 32 + 1的空間即可(+1是為了防止數據小于32和向上取整)。

    當要操作一個數據時,先將其除以32來判斷它應該處于數組中哪一個整型中。再對其%32,來判斷它位于這個整型中的哪一個位上,此時再進行對應的位運算即可。


    set

    set即將對應標識位置1
    可以通過將1左移pos個位置,再讓對應位置與這個數據相即可實現。

    //數據的對應標識位置1 void set(size_t x) {//計算出在數組中哪一個整型中size_t index = x >> 5;//計算出在該整型的哪一個位上size_t pos = x % 32;//對應位置 置1_bits[index] |= (1 << pos);++_size; }

    reset

    reset即將對應標識位置0

    首先讓1左移pos個位置,再對這個數據進行取反。然后讓對應位置數據與這個數據相即可。

    //數據的對應標識位置0 void reset(size_t x) {size_t index = x >> 5;size_t pos = x % 32;//對應位置數據置零_bits[index] &= ~(1 << pos);++_size; }

    test即判斷這個數據在不在,只需要讓1左移pos個位置,再用對應位置進行與運算,如果為1則說明存在,0則說明不存在

    test

    bool test(size_t x) const {size_t index = x >> 5;size_t pos = x % 32;return _bits[index] & (1 << pos); }

    完整代碼

    #pragma once #include<vector>namespace lee {class bitset{public://每一個位標識一個數據,一個整型4個字節,可存儲32個位, 所以需要/32(或者右移五位)。+1是為了取整bitset(size_t size = 32) : _bits((size >> 5) + 1, 0), _size(0){}//數據的對應標識位置1void set(size_t x){//計算出在數組中哪一個整型中size_t index = x >> 5;//計算出在該整型的哪一個位上size_t pos = x % 32;//對應位置 置1_bits[index] |= (1 << pos);++_size;}//數據的對應標識位置0void reset(size_t x){size_t index = x >> 5;size_t pos = x % 32;//對應位置數據置零_bits[index] &= ~(1 << pos);++_size;}//判斷數據是否存在bool test(size_t x) const{size_t index = x >> 5;size_t pos = x % 32;return _bits[index] & (1 << pos);}size_t size() const{return _size;}private:std::vector<int> _bits;size_t _size;}; };

    布隆過濾器

    我們在使用新聞客戶端看新聞時,它會給我們不停地推薦新的內容,它每次推薦時要去重,去掉那些已經看
    過的內容。問題來了,新聞客戶端推薦系統如何實現推送去重的? 用服務器記錄了用戶看過的所有歷史記
    錄,當推薦系統推薦新聞時會從每個用戶的歷史記錄里進行篩選,過濾掉那些已經存在的記錄。 如何快速查
    找呢?

  • 用哈希表存儲用戶記錄,缺點:浪費空間
  • 用位圖存儲用戶記錄,缺點:不能處理哈希沖突
  • 將哈希與位圖結合,即布隆過濾器

  • 布隆過濾器概念

    布隆過濾器是由布隆(Burton Howard Bloom)在1970年提出的 一種緊湊型的、比較巧妙的概率型數據結 構,特點是高效地插入和查詢,可以用來告訴你 “某樣東西一定不存在或者可能存在”,它是用多個哈希函
    數,將一個數據映射到位圖結構中。此種方式不僅可以提升查詢效率,也可以節省大量的內存空間。


    布隆過濾器的優缺點

    優點

  • 增加和查詢元素的時間復雜度為:O(K), (K為哈希函數的個數,一般比較小),與數據量大小無關
  • 哈希函數相互之間沒有關系,方便硬件并行運算
  • 布隆過濾器不需要存儲元素本身,在某些對保密要求比較嚴格的場合有很大優勢
  • 在能夠承受一定的誤判時,布隆過濾器比其他數據結構有這很大的空間優勢
  • 數據量很大時,布隆過濾器可以表示全集,其他數據結構不能
  • 使用同一組散列函數的布隆過濾器可以進行交、并、差運算
  • 缺點

  • 有誤判率,即存在假陽性(False Position),即不能準確判斷元素是否在集合中(補救方法:再建立一個白 名單,存儲可能會誤判的數據)
  • 不能獲取元素本身
  • 一般情況下不能從布隆過濾器中刪除元素
  • 如果采用計數方式刪除,可能會存在計數回繞問題

  • 布隆過濾器的實現思路

    這里底層使用的數據結構是前面實現的位圖,所以對應操作可以直接到上面看。

    哈希沖突的問題

    之前在哈希那一章說過,當字符串使用哈希時,無可避免的會出現哈希沖突的問題,而位圖又是一個不能解決哈希沖突的數據結構,所以這就導致了一個問題,對于一個數據不能只有一個位置來標記,需要用到多個位置。于是我們需要用到多個哈希函數,來將數據映射到多個位置上面,才能確保數據的準確性。

    例如下面的baidu,分別通過三種哈希函數映射到了1,4,7。將這三個位置全部置1


    這里我使用了三個字符串哈希函數,分別是BKDR,SDBM,RS。

    struct _BKDRHash {//BKDRHashsize_t operator()(const std::string& key){size_t hash = 0;for (size_t i = 0; i < key.size(); i++){hash *= 131;hash += key[i];}return hash;} };struct _SDBMHash {//SDBMHashsize_t operator()(const std::string& key){size_t hash = 0;for (size_t i = 0; i < key.size(); i++){hash *= 65599;hash += key[i];}return hash;} };struct _RSHash {//RSHashsize_t operator()(const std::string& key){size_t hash = 0;size_t magic = 63689;for (size_t i = 0; i < key.size(); i++){hash *= magic;hash += key[i];magic *= 378551;}return hash;} };

    如何選擇哈希函數個數和布隆過濾器長度

    而如果一個數據要映射多個位置,如果布隆過濾器較小,則會導致數據馬上全部映射滿,此時無論進行什么操作,都會存在大量的誤報率。也就是說,布隆過濾器的長度與誤報率成反比,與空間利用率成反比。
    并且哈希函數的個數也值得思考,哈希函數越多,映射的位置也就越多,此時準確性也就越高,但隨之帶來的問題就是效率的降低。也就是說,哈希函數的個數與效率成反比,準確率成正比


    這張圖則是各種長度以及哈希函數的效率對比圖。

    那么該如何選擇哈希函數的個數以及布隆過濾器的長度呢?
    這里引用一位大佬計算出的公式,具體求解鏈接我放在了文章末尾

    k 為哈希函數個數,m 為布隆過濾器長度,n 為插入的元素個數,p 為誤報率。

    所以根據公式,我這里使用的哈希函數為3個,空間就應該開插入元素個數的五倍。


    插入

    數據分別映射到三個位置上,將三個位置全部置1

    void set(const K& key) {//為了減少錯誤率,用多個哈希函數將同一個數據映射到多個位置size_t pos1 = Hash1()(key) % _capacity;size_t pos2 = Hash2()(key) % _capacity;size_t pos3 = Hash3()(key) % _capacity;_bs.set(pos1);_bs.set(pos2);_bs.set(pos3);++_size; }

    查找

    布隆過濾器的查找即分別查找映射位,一旦有任何一個為0,則說明數據不存在。如果全部為1,此時說明數據可能存在,因為可能存在將別人映射的位置誤判進來,所以布隆過濾器的查找是不夠準確的。所以可以這么說,布隆過濾器只提供模糊查詢,如果需要精確查詢,只能使用別的方法。

    布隆過濾器如果說某個元素不存在時,該元素一定不存在,如果該元素存在時,該元素可能存在,因
    為有些哈希函數存在一定的誤判。

    bool test(const K& key) {size_t pos1 = Hash1()(key) % _capacity;size_t pos2 = Hash2()(key) % _capacity;size_t pos3 = Hash3()(key) % _capacity;if (!_bs.test(pos1) || !_bs.test(pos2) || !_bs.test(pos3)){return false;}return true; }

    刪除

    布隆過濾器是不支持刪除操作的,因為一旦進行刪除,很可能就會將別人映射的位置也置為0,導致出現錯誤。

    但是如果非要刪除的話,也不是不行。
    可以將每一個比特位拓展為一個計數器,每當有數據插入時對應位置的計數器+1,數據刪除是對應位的計數器-1。一個位肯定無法完成計數,需要用到多個位,此時就會導致存儲空間的大量增加,使得效率下降,而本身選擇布隆過濾器也是為了節省空間,這樣就本末倒置了。


    完整代碼

    #pragma once #include"bitset.hpp" #include<string> namespace lee {struct _BKDRHash{//BKDRHashsize_t operator()(const std::string& key){ size_t hash = 0;for (size_t i = 0; i < key.size(); i++){hash *= 131;hash += key[i];}return hash;}};struct _SDBMHash{//SDBMHashsize_t operator()(const std::string& key){size_t hash = 0;for (size_t i = 0; i < key.size(); i++){hash *= 65599;hash += key[i];}return hash;}};struct _RSHash{//RSHashsize_t operator()(const std::string& key){size_t hash = 0;size_t magic = 63689;for (size_t i = 0; i < key.size(); i++){hash *= magic;hash += key[i];magic *= 378551;}return hash;}};template<class K = std::string, class Hash1 = _BKDRHash, class Hash2 = _SDBMHash, class Hash3 = _RSHash>class BloomFilter{public:BloomFilter(size_t num): _bs(num), _capacity(num), _size(0){}void set(const K& key){//為了減少錯誤率,用多個哈希函數將同一個數據映射到多個位置size_t pos1 = Hash1()(key) % _capacity;size_t pos2 = Hash2()(key) % _capacity;size_t pos3 = Hash3()(key) % _capacity;_bs.set(pos1);_bs.set(pos2);_bs.set(pos3);++_size;}bool test(const K& key){size_t pos1 = Hash1()(key) % _capacity;size_t pos2 = Hash2()(key) % _capacity;size_t pos3 = Hash3()(key) % _capacity;if (!_bs.test(pos1) || !_bs.test(pos2) || !_bs.test(pos3)){return false;}return true;}size_t size() const{return _size;}private:lee::bitset _bs;size_t _size;size_t _capacity;}; };

    參考文章:

    • 詳解布隆過濾器的原理、使用場景和注意事項

    總結

    以上是生活随笔為你收集整理的海量数据处理(一) :位图与布隆过滤器的概念以及实现的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。