是否因爲hbase中的布爾過濾器的實現而感到疑惑呢?其實布爾過濾器這種存儲結構的是基於多位圖的,其內部存儲的是多個位圖結構。本篇我們瞭解一下位圖這種數據結構。
本質
位圖其本質就是利用hash函數映射的一種map數據結構,我們知道java中的hashmap數據結構其底層仍然是以hash函數映射的數字作爲key,其value是以鏈表or數組的方式存儲數據的,一旦使用了hash函數,就意味着存在hash碰撞的可能性。
位圖的存在,其本質就是對hashMap的一種更加節約內存的優化,其目的就是檢測一個值是否存在或不存在,在一些應用場景,比如爬蟲結構中的url去重功能,緩存實現是否已經被加載過等等,也就是說在存在性命題的應用場景中會經常碰到位圖數據結構的應用,
位圖的特點
1. 查詢高效(O(1)複雜度)
2. 節約內存(1億不同的key,總共需要12.5m)
我們知道,使用hashMap的key值,我們是可以約定數據類型的比如,integer/long/string等等數據結構作爲key的,因此在某種意義上來講,key所存儲的空間在一開始就被定死了,那能否充分利用key的大小,最大程度
位圖示意圖
通過上圖,主要想表達,位圖,只有一個大數組(以數組形式構造的map結構),並且利用數組在高速緩存的連續性特點,能夠高效地讀取做操作,在位圖中的key是通過hash函數映射成一個整形值,並根據該整形值查找相應數組的位置,並設置相應數據值中的位數置爲1即可。
位圖映射規則
位圖映射的規則就是 爲一長串的二進制數的對應位標記爲1就行了,其優點就是不用像存於hashmap的key那樣耗內存
比如我們存儲的是數組中的第一二個字節:
1000000000001000 表示在位圖中存儲的是16 與 4 這兩個數
如何判斷位圖中是否存在某數
依據上面的例子 當我們判斷位圖中是否存在4的時候,只要取出這個數字對應在位圖中的字符元素 並與 4本身的二進制位進行 合併(&)運算,只要其計算結果等於4本身的二進制就可以了
代碼實現
import com.carrotsearch.sizeof.RamUsageEstimator;
class MyBitSet{
private final char[] aChar; // 我們定義一個字符數組其大小
private final int size;
public MyBitSet(int size){
// 初始化一個可以存size大小的位圖
// 這裏當size爲16時候,由於每個char元素佔用16位,所以,我們初始化
// 1個字節的字節數組就可以了
aChar = new char[ size % 16 == 0? size / 16 : size /16 +1];
this.size = size;
}
///存儲i數字
public void set(int i) {
// 當我們i能被16整除的時候
int index = i % 16 == 0? i / 16 - 1 : i /16;
int splitindex = 1 << (i - index * 16 - 1);
aChar[index] = (char) (aChar[index] | splitindex);
}
public boolean get(int i) {
int index = i % 16 == 0? i / 16 -1 : i /16;
int splitindex = 1 << (i - index * 16 - 1);
int i1 = aChar[index] & splitindex;
return i1 == splitindex;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < aChar.length; i++) {
sb.append(aChar[i]);
System.out.println(Integer.toBinaryString(aChar[i]));
}
return sb.toString();
}
public static void main(String[] args) {
int size = 1234567890; // 存儲12億3千萬個數組
MyBitSet myBitSet = new MyBitSet(size);
myBitSet.set(1);
myBitSet.set(2);
myBitSet.set(3);
myBitSet.set(31);
myBitSet.set(32);
myBitSet.set(16);
for (int i = 1; i <= size; i++) {
boolean b = myBitSet.get(i);
if(b) System.out.println(i);
}
long l = RamUsageEstimator.sizeOf(myBitSet);
System.out.println("size: "+l); // size: 154321032 140m數據
}
}
我們使用com.carrotsearch.java-sizeof:0.05 包中的計算對象大小的函數,可以獲得在位圖中存儲12億的數字,只需要140m左右的內存空間,
如果使用hashmap會有多大呢?據估計要400m左右
因此位圖存儲數值的內存空間比hashmap更節約內存,而且在O(1)的時間複雜度內就能定位到元素,比hashmap會有一定的性能提升,因爲hashmap在查找值的時候不僅要定位到hashmap指定key上,還要對key所鏈接的元素進行逐一比對。
java實現類
其實java也有內置的位圖已經實現好了
package java.util.BitSet