位圖(Bitmap),即位(Bit)的集合,是一種數據結構,可用於記錄大量的0-1狀態,在很多地方都會用到,比如Linux內核(如inode,磁盤塊)、Bloom Filter算法等,其優勢是可以在一個非常高的空間利用率下保存大量0-1狀態。
BitMap的原理
BitMap 的基本原理就是用一個bit 位來存放某種狀態,適用於大規模數據,但數據狀態又不是很多的情況。通常是用來判斷某個數據存不存在的。
舉例:在Java里面一個int類型占4個字節,假如要對於10億個int數據進行處理呢?10億*4/1024/1024/1024=4個G左右,需要4個G的內存。
如果能夠采用bit儲,10_0000_0000Bit=1_2500_0000byte=122070KB=119MB, 那么在存儲空間方面可以大大節省。
在Java里面,BitMap已經有對應實現的數據結構類java.util.BitSet,BitSet的底層使用的是long類型的數組來存儲元素。
我們來看看具體存儲:
對於1,3,5,7這四個數,如果存在的話,則可以這樣表示:
1代表這個數存在,0代表不存在。例如表中01010101代表1,3,5,7存在,0,2,4,6不存在。那如果8,10,14也存在怎么存呢?如圖,8,10,14我們可以存在第二個字節里
以此類推。
Map映射表
假設需要排序或者查找的總數N=10000000,那么我們需要申請內存空間的大小為int a[1 + N/32],其中:a[0]在內存中占32為可以對應十進制數0-31,依次類推:
bitmap表為:
a[0]--------->0-31
a[1]--------->32-63
a[2]--------->64-95
a[3]--------->96-127
..........
BitMap算法處理大數據問題的場景:
(1)給定10億個不重復的正int的整數,沒排過序的,然后再給一個數,如何快速判斷這個數是否在那10億個數當中。
解法:遍歷40個億數字,映射到BitMap中,然后對於給出的數,直接判斷指定的位上存在不存在即可。
(2)使用位圖法判斷正整形數組是否存在重復
解法:遍歷一遍,存在之后設置成1,每次放之前先判斷是否存在,如果存在,就代表該元素重復。
(3)使用位圖法進行元素不重復的正整形數組排序
解法:遍歷一遍,設置狀態1,然后再次遍歷,對狀態等於1的進行輸出,參考計數排序的原理。
(4)在2.5億個整數中找出不重復的正整數,注,內存不足以容納這2.5億個整數
解法1:采用2-Bitmap(每個數分配2bit,00表示不存在,01表示出現一次,10表示多次,11無意義)。
解法2:采用兩個BitMap,即第一個Bitmap存儲的是整數是否出現,接着,在之后的遍歷先判斷第一個BitMap里面是否出現過,如果出現就設置第二個BitMap對應的位置也為1,最后遍歷BitMap,僅僅在一個BitMap中出現過的元素,就是不重復的整數。
解法3:分治+Hash取模,拆分成多個小文件,然后一個個文件讀取,直到內存裝的下,然后采用Hash+Count的方式判斷即可。
該類問題的變形問題,如已知某個文件內包含一些電話號碼,每個號碼為8位數字,統計不同號碼的個數。8位最多99 999 999,大概需要99m個bit,大概10幾m字節的內存即可。 (可以理解為從0-99 999 999的數字,每個數字對應一個Bit位,所以只需要99M個Bit==12MBytes,這樣,就用了小小的12M左右的內存表示了所有的8位數的電話)
BitMap的一些缺點:
(1)數據碰撞。比如將字符串映射到 BitMap 的時候會有碰撞的問題,那就可以考慮用 Bloom Filter 來解決,Bloom Filter 使用多個 Hash 函數來減少沖突的概率。
(2)數據稀疏。又比如要存入(10,8887983,93452134)這三個數據,我們需要建立一個 99999999 長度的 BitMap ,但是實際上只存了3個數據,這時候就有很大的空間浪費,碰到這種問題的話,可以通過引入 Roaring BitMap 來解決。
例子:
從正整數數組中尋找重復的整數
import java.util.BitSet; import java.util.HashSet; import java.util.Set; public class TestBitMap { //假設數據是以數組的形式給我們的 public static Set test(int[] arr) { int j = 0; //避免返回重復的數,存在Set里 Set output = new HashSet(); BitSet bitSet = new BitSet(Integer.MAX_VALUE); int i = 0; while (i < arr.length) { int value = arr[i]; //判斷該數是否存在bitSet里 if (bitSet.get(value)) { output.add(value); } else { bitSet.set(value, true); } i++; } return output; } //測試 public static void main(String[] args) { int[] t = {1,2,3,4,5,6,7,8,3,4,9}; Set t2 = test(t); System.out.println(t2); } }
總結
本文主要介紹了BitMap算法的基本原理和應用案例,其本質上是采用了bit位來表示元素狀態,從而在特定場景下能夠極大的節省存儲空間,非常適合對海量數據的查找,判重,刪除等問題的處理。
其他參考:
https://www.cnblogs.com/hongdada/p/8267032.html
https://www.cnblogs.com/gczr/p/7358813.html