上篇我們講了BitMap是如何對資料進行儲存的,沒看過的可以看一下【演算法與資料結構專場】BitMap演算法介紹
這篇我們來講一下BitMap這個資料結構的程式碼實現。
回顧下資料的儲存原理
一個二進位制位對應一個非負數n,如果n存在,則對應的二進位制位的值為1,否則為0。
這個時候,我們的第一個問題:
我們在使用byte,int,short,long等這些資料型別在儲存資料的時候,他們最小的都要佔用一個位元組的記憶體,也就是8個bit,也就是說,最小的操作單位是8個bit。根本就沒有可以一個一個bit位操作的資料型別啊。
在Java的bitMaP實現中,它採用的是用一個long資料來進行儲存的。一個long佔用8個位元組,即64bit,所以一個long可以儲存64個數。例如 arr 是一個long 型別的陣列,則 arr[0]可以存 0 ~ 63,arr[1]可以存64 ~127,以此類推。
不過,我們就採用byte陣列的來存吧。一個byte佔用一個位元組,即8bit,可以存8個數字。
當然,你要採用long陣列來存也可以。在實現上可以說是一樣的。
例如我們要儲存(1,3,5,7,8,10)時,他們的記憶體如下所示。
下面我們就來講講如何對一個一個位進行操作的。
如何向bitmap中新增一個數值
我們先來說說如何在bitmap中如何新增一個數值的問題,例如我們我們要新增n=14。
這個其實很簡單,我們先找到n在arr陣列中的下標index,顯然index = 1。然後再找到n在arr[index]中的位置position,顯然這裡position = 6。
這裡還是可以很容易找出index和position的公式的。即
index = n / 8 = n >> 3。
position = n % 8 = n & 0x07。
接下來我們把1向右移動position個二進位制位,然後把所得的結果和arr[index]做“或(or)”操作就可以了。如下圖
這裡有個需要注意的地方,在畫圖的時候,為了方便,我們是把左邊的位當作低位,右邊的位當作高位來算了。不過在實際的儲存中,左邊的才是存高位,而右邊的存的是低位。所以在我們的程式碼實現中,我們所說的右移對應程式碼的左移。
程式碼實現
//新增資料的操作
public void add(int n){
//用>>的操作是,運算會比較快
int index = n >> 3;
int position = n & 0x07;
//把1右移和做or操作兩步一起
//即 << 對應上圖的右移,實際上<<是左移符。
arr[index] |= 1 << position;
}
知道了add操作,其他的操作差不多類似。
當然,我們實現的add操作只是簡單的實現一下,假如你要嚴謹地實現的話,還是需要很多異常的判斷的。例如判斷這個數是否是非負數,判斷arr陣列是否下標越界,進行容量的擴充等等。有興趣的可以嚴謹去實現一下。
刪除操作。
我們只需要把對應的二進位制的1變成0就可以了。
我們可以把1右移(程式碼中對應左移)後的結果取反,然後與arr[index]做“與”操作就可以了。程式碼如下:
public void delete(int n){
int index = n >> 3;
int position = n & 0x07;
arr[index] &= ~(1 << position);
}
判斷是否存在操作
我們把1右移之後,把結果和arr[index]做“與”操作,如何結果不為0,則證明存在,否則就不存在。
public boolean contain(int n){
int index = n >> 3;
int position = n & 0x07;
return (arr[index] & (1 << position)) != 0;
}
三個最基本的操作程式碼基本實現了。
希望大家能夠去實踐一下。
全部程式碼:
public class BitMap {
private byte[] arr;
//容量,即最多能夠存多少個資料
private int capacity;
public BitMap(int capacity) {
this.capacity = capacity;
//一個byte可以存8個資料,capacity實際上指的是多少個bit
arr = new byte[(capacity / 8 + 1)];
}
//新增資料的操作
public void add(int n){
//用>>的操作是,運算會比較快
int index = n >> 3;
int position = n & 0x07;
//把1右移和做or操作兩步一起
//即 << 對應上圖的右移,實際上<<是左移符。
arr[index] |= 1 << position;
}
public void delete(int n){
int index = n >> 3;
int position = n & 0x07;
arr[index] &= ~(1 << position);
}
public boolean contain(int n){
int index = n >> 3;
int position = n & 0x07;
return (arr[index] & (1 << position)) != 0;
}
}
問題
大家看了以上的程式碼,有沒發現一些問題呢?
例如我們只在bitmap儲存1個數,並且存的數值是2000000000,我們就會在第2000000000個二進位制把0改為1。也就是說arr陣列的大小至少為2000000000/8+1。可是這時候前面的二進位制位並沒有存資料,那不是超級超級浪費資源?
所以說,像我們上面的那種寫法可以說是暴力寫法,沒有經過任何優化,實際上,在Java自帶的bitMap中是有很多優化的,並不會像我們上面實現的程式碼一樣那麼浪費空間資源。有興趣的可以研究下。
至於如何優化,我會在之後的文章講,盡情期待。
獲取更多原創文章,可以關注下我的公眾號:苦逼的碼農,我會不定期分享一些資源和軟體等。後臺回覆禮包送你一份時下熱門的資源大禮包同時也感謝把文章介紹給更多需要的人