教你用BitMap排序、查找和存储大量数据
Bit-map的基本思想就是用一個bit位來標記某個元素對應的Value,而Key即是該元素。由于采用了Bit為單位來存儲數據,因此在存儲空間方面,可以大大節省。
Bit-map概述
假設現在有這樣一個需求:在20億個隨機整數中找出某個數是否存在其中,并假設32位操作系統,4G內存,你會怎么做?我們知道在java中,一個int占4字節,1字節=8位(1 byte = 8 bit),如果每個數字用int存儲,那就是20億個int,因而占用的空間約為:
2000000000*4/1024/1024/1024≈7.45 G顯然消耗的內存空間太多了。如果按位存儲就不一樣了,20億個數就是20億位,占用空間約為:
2000000000/8/1024/1024/1024≈0.23 G那么使用Bit-map到底是怎么來存儲的以及它的過程是什么樣子的呢?接下里帶大家一起研究一下。
在最開始的時候我們說過,Bit-map每一位表示一個數,0表示不存在,1表示存在。例如我們要存儲數字{1,3,4,6},則可以使用Bit-map存儲結構如下:
計算機內存分配的最小單位是字節,也就是8位,那如果要表示{12,13,15}怎么辦呢?當然是在另一個8位上表示了,如下圖所示:
大家有沒有感覺這樣得結構有點眼熟?不錯,好像變成一個二維數組。我們知道1個int占32位,如果我們要存儲得數字得最大數字是N,則我們只需要申請一個int數組長度為 int b[1+N/32] 即可存儲,其中:
這樣,給定任意整數N,那么N/32就得到下標,N%32就知道它在此下標的哪個位置。
Bit-map操作原理
以上簡單概括性的對Bit-map的原理進行了介紹,接下來對于常用的操作我們具體看一下是怎么對數據進行處理的。
添加數據
如果現在我們想把5這個數字放進去,怎么做呢?上面提到了,給定任意整數N,那么N/32就得到下標,N%32就知道它在此下標b的哪個位置。首先,5/32=0,也是說它應該在b[0],5%32=5,說明它應該在b[0]的第5個位置,那我們把1向左移動5位,然后與原數據按位或:
換成二進制就是:01001110|0010000=01101110,這就相當于 78 | 32 = 110,也就是說,要想插入一個數,將1左移代表該數字的那一位,然后與原數進行按位或操作,即:
因此,公式可以概括為:p + (i/8)|(1<<(i%8)) 其中,p表示現在的值,i表示待插入的數。
清除數據
假設我們要移除某個數字N,該怎么做呢?只需將該數所在的位置為0即可,那么怎么可以做到呢?為了便于理解,我們還是將結構看成是一個二位數組。則步驟可以歸納如下:
假設我們要6移除,我們用圖示來表示以一下執行過程:
查找數據
前面我們也說了,每一位代表一個數字,1表示有(或者說存在),0表示無(或者說不存在)。通過把該為置為1或者0來達到添加和清除的效果,那么判斷一個數存不存在就是判斷該數所在的位是0還是1。所以查找相對來說比較簡單。假設,我們想知道6在不在,那么只需判斷 b[0] & (1<<6) 如果這個值是0,則不存在,如果是1,就表示存在。
Bitmap用途
快速排序
假設我們要對0-7內的5個元素(4,7,2,5,3)排序(這里假設這些元素沒有重復),我們就可以采用Bit-map的方法來達到排序的目的,要表示8個數,我們就只需要8個Bit(1Bytes):
優點:
- 運算效率高,不需要進行比較和移位;
- 占用內存少,比如N=10000000,只需占用內存為N/8=1250000Byte=1.25M。
缺點:
- 所有的數據不能重復,即不可對重復的數據進行排序和查找;
- 只有當數據比較密集時才有優勢
快速去重
假設讓你從20億個整數中找出不重復的整數的個數,提前是內存不足以容納這20億個整數,那么你會用什么辦法?使用Bit-map就可以很好的解決。關鍵的問題就是怎么設計Bit-map來表示這20億個數字的狀態了。
一個數字的狀態只有三種,分別為不存在,只有一個,有重復。因此,我們只需要2bits就可以對一個數字的狀態進行存儲了,假設我們設定一個數字不存在為00,存在一次01,存在兩次及其以上為11,那我們大概需要存儲空間2G左右。所以可以這樣進行操作:
快速查找
int數組中的一個元素是4字節占32位,那么除以32就知道元素的下標,對32求余數(%32)就知道它在哪一位,如果該位是1,則表示存在。
Bloom Filters
Bloom Filters概述
Bloom filter 是一個數據結構,它可以用來判斷某個元素是否在集合內,具有運行快速,內存占用小的特點。這個存儲的原理與Bit-map類似,所以我們在這里一并介紹一下。
Bloom Filter 是一個基于概率的數據結構:它只能告訴我們一個元素絕對不在集合內或可能在集合內。如果想判斷一個元素是不是在一個集合里,一般想到的是將集合中所有元素保存起來,然后通過比較確定。鏈表、樹、散列表(哈希表)等等數據結構都是這種思路,但是隨著集合中元素的增加,需要的存儲空間越來越大;同時檢索速度也越來越慢,檢索時間復雜度分別是O(n)、O(log n)、O(1)。
布隆過濾器的原理是:
- 當一個元素被加入集合時,通過 K 個散列函數將這個元素映射成一個位數組(Bit array)中的 K 個點,把它們置為 1 。
- 檢索時,只要看看這些點是不是都是1就知道元素是否在集合中;
- 如果這些點有任何一個 0,則被檢元素一定不在;
- 如果都是1,則被檢元素很可能在(之所以說“可能”是誤差的存在),也可能不在。
Bloom Filters流程
在實際的項目應用中,布隆過濾器經常會被用在一些大規模去重,但又允許有小概率誤差的場景中,例如說我們對一組爬蟲網頁地址的去重操作,或者統計某些大型網站每天的用戶訪問數量(需要對相同用戶的多次訪問進行去重),還可以解決緩存穿透問題等。
實際上,關于bitmap和布隆過濾器這類工具在大型互聯網企業上已經受到了廣泛使用,例如說java里面提供了BitSet類,Redis也提供了相應的位圖類,Google里面的guava工具包中的BloomFilter也已經實現類布隆過濾器,所以在實際應用的時候只需要直接使用這些現有的組件即可,避免重復造輪子的情況發生。
總結
以上是生活随笔為你收集整理的教你用BitMap排序、查找和存储大量数据的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 带你玩转关键字Synchronized
- 下一篇: MYSQL专题-使用Binlog日志恢复