BitMap BloomFilter BitSet詳解

1. BitMap

BitMap利用內存中連續的二進制位,用於對大量數據做去重和查詢
例如,給定一塊長度爲10bit的內存空間,想要依次插入整形數據4,2,1,3,我們按照如下的步驟來做:
在這裏插入圖片描述

package BitMap;
/**
 * 位圖BitMap實現代碼
 * @author xjh 2010.01.18
 */
public class bitmap {
    public char[] bytes;
    public int nbits;

    public bitmap(int nbits){
        this.nbits=nbits;
        this.bytes=new char[nbits/8+1];
            //一個byte佔8位 換言之1byte=8bit
    }

    public void set(int k){
        if (k>nbits) return;
        int byteIndex=k/8;
        int bitIndex=k%8;
        bytes[byteIndex]|=(1<<bitIndex);    //1轉成二進制 然後左移bitIndex位
    }

    public boolean get(int k){
        if (k>nbits) return false;
        int byteIndex=k/8;
        int bitIndex=k%8;
        return (bytes[byteIndex]&(1<<bitIndex))!=0;
    }
}

關於BitMap的案例,這裏推薦程序員小灰的一篇博客:https://mp.weixin.qq.com/s/xxauNrJY9HlVNvLrL5j2hg

2. Bloom Filter

在這裏插入圖片描述
布隆過濾器的誤判有一個特點,它只對存在的情況有誤判。如果某個數字經過布隆過濾器判斷不存在,那說明這個數字真的不存在,不會發生誤判;如果某個數字經過布隆過濾器判斷存在,這個時候可能有誤判,有可能是不存在的。不過,只要我們調正哈希函數的個數、位圖大小跟要存儲數字的個數之間的比例,那可以將這種誤判的概率講到非常低。
Google的Guava工具提供了BloomFilter的實現。

總結:常用於大數據的排重,比如email,url 等。 核心原理:將每條數據通過計算產生一個指紋(一個字節或多個字節,但一定比原始數據要少很多),其中每一位都是通過隨機計算獲得,在將指紋映射到一個大的按位存儲的空間中。注意:會有一定的錯誤率。
優點:空間和時間效率都很高。 布隆過濾器存儲空間和插入 / 查詢時間都是常數O(k)。另外, 散列函數相互之間沒有關係,方便由硬件並行實現。布隆過濾器不需要存儲元素本身,在某些對保密要求非常嚴格的場合有優勢。
缺點:隨着存入的元素數量增加,誤算率隨之增加。

關於bloomFilter在url查重中的運用,這裏推薦一篇博客:網絡爬蟲:URL去重策略之布隆過濾器(BloomFilter)的使用

3.BitSet

原理簡介:
Java平臺的BitSet用於存放一個位序列,如果要高效的存放一個位序列,就可以使用位集(BitSet)。由於位集將位包裝在字節裏,所以使用位集比使用Boolean對象的List更加高效和更加節省存儲空間。
BitSet是位操作的對象,值只有0或1即false和true,內部維護了一個long數組,初始只有一個long,所以BitSet最小的size是64,當隨着存儲的元素越來越多,BitSet內部會動態擴充,一次擴充64位,最終內部是由N個long來存儲
默認情況下,BitSet的所有位都是false即0。
在沒有外部同步的情況下,多個線程操作一個BitSet是不安全的

一個1GB的空間,有8102410241024 = 8.5810^9bit,也就是1GB的空間可以表示85億多個數。

應用場景:

  1. 統計一組大數據中沒有出現過的數;
     將這組數據映射到BitSet,然後遍歷BitSet,對應位爲0的數表示沒有出現過的數據。
    
  2. 對大數據進行排序;
     將數據映射到BitSet,遍歷BitSet得到的就是有序數據。
    
  3. 在內存對大數據進行壓縮存儲等等。
     一個GB的內存空間可以存儲85億多個數,可以有效實現數據的壓縮存儲,節省內存空間開銷。
    

爲什麼BitSet使用long數組做內部存儲?
JDK選擇long數組作爲BitSet的內部存儲結構是出於性能的考慮,因爲BitSet提供and和or這種操作,需要對兩個BitSet中的所有bit位做and或者or,實現的時候需要遍歷所有的數組元素。使用long能夠使得循環的次數降到最低,所以Java選擇使用long數組作爲BitSet的內部存儲結構。
從數據在棧上的存儲來說,使用long和byte基本是沒有什麼差別的,除了編譯器強制地址對齊的時候,使用byte最多會浪費7個字節(強制按照8的倍數做地址對其),另外從內存讀數組元素的時候,也是沒有什麼區別的,因爲彙編指令有對不同長度數據的mov指令。所以說,JDK選擇使用long數組作爲BitSet的內部存儲結構的根本原因就是在and和or的時候減少循環次數,提高性能。
例如我們進行BitSet中的and, or,xor操作時,要對整個bitset中的bit都進行操作,需要依次讀出bitset中所有的word,如果是long數組存儲,我們可以每次讀入64個bit,而int數組存儲時,只能每次讀入32個bit。另外我們在查找bitset中下一個置爲1的bit時,word首先會和0進行比較,如果word的值爲0,則表示該word中沒有爲1的bit,可以忽略這個word,如果是long數組存儲,可以一次跳過64個bit,如果是int數組存儲時,一次只能跳過32個bit。

參考文獻:
1.Java BitSet(位集)
2.大量數據去重:Bitmap和布隆過濾器(Bloom Filter)

發佈了65 篇原創文章 · 獲贊 20 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章