Bit-map的基本思想就是用一個bit位來標記某個元素對應的Value,而Key便是該元素。因爲採用了Bit爲單位來存儲數據,所以在存儲空間方面,能夠大大節省。(PS:劃重點 節省存儲空間 )算法
假設有這樣一個需求:在20億個隨機整數中找出某個數m是否存在其中,並假設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函數
高下立判,無需多言google
那麼,問題來了,如何表示一個數呢?spa
剛纔說了,每一位表示一個數,0表示不存在,1表示存在,這正符合二進制操作系統
這樣咱們能夠很容易表示{1,2,4,6}這幾個數:設計
圖片code
計算機內存分配的最小單位是字節,也就是8位,那若是要表示{12,13,15}怎麼辦呢?
固然是在另外一個8位上表示了:
圖片
這樣的話,好像變成一個二維數組了
1個int佔32位,那麼咱們只須要申請一個int數組長度爲 int tmp[1+N/32] 便可存儲,其中N表示要存儲的這些數中的最大值,因而乎:
tmp[0]:能夠表示0~31
tmp[1]:能夠表示32~63
tmp[2]:能夠表示64~95
。。。
如此一來,給定任意整數M,那麼M/32就獲得下標,M%32就知道它在此下標的哪一個位置
這裏有個問題,咱們怎麼把一個數放進去呢?例如,想把5這個數字放進去,怎麼作呢?
首先,5/32=0,5%32=5,也是說它應該在tmp[0]的第5個位置,那咱們把1向左移動5位,而後按位或
圖片
換成二進制就是
圖片
這就至關於 86 | 32 = 118
86 | (1<<5) = 118
b[0] = b[0] | (1<<5)
也就是說,要想插入一個數,將1左移帶表明該數字的那一位,而後與原數進行按位或操做
化簡一下,就是 86 + (5/8) | (1<<(5%8))
所以,公式能夠歸納爲:p + (i/8)|(1<<(i%8)) 其中,p表示如今的值,i表示待插入的數
以上是添加,那若是要清除該怎麼作呢?
仍是上面的例子,假設咱們要6移除,該怎麼作呢?
圖片
從圖上看,只需將該數所在的位置爲0便可
1左移6位,就到達6這個數字所表明的位,而後按位取反,最後與原數按位與,這樣就把該位置爲0了
b[0] = b[0] & (~(1<<6))
b[0] = b[0] & (~(1<<(i%8)))
前面咱們也說了,每一位表明一個數字,1表示有(或者說存在),0表示無(或者說不存在)。經過把該爲置爲1或者0來達到添加和清除的小夥,那麼判斷一個數存不存在就是判斷該數所在的位是0仍是1
假設,咱們想知道3在不在,那麼只需判斷 b[0] & (1<<3) 若是這個值是0,則不存在,若是是1,就表示存在
大量數據的快速排序、查找、去重
假設咱們要對0-7內的5個元素(4,7,2,5,3)排序(這裏假設這些元素沒有重複),咱們就能夠採用Bit-map的方法來達到排序的目的。
要表示8個數,咱們就只須要8個Bit(1Bytes),首先咱們開闢1Byte的空間,將這些空間的全部Bit位都置爲0,而後將對應位置爲1。
最後,遍歷一遍Bit區域,將該位是一的位的編號輸出(2,3,4,5,7),這樣就達到了排序的目的,時間複雜度O(n)。
優勢:
缺點:
20億個整數中找出不重複的整數的個數,內存不足以容納這20億個整數。
首先,根據「內存空間不足以容納這05億個整數」咱們能夠快速的聯想到Bit-map。下邊關鍵的問題就是怎麼設計咱們的Bit-map來表示這20億個數字的狀態了。其實這個問題很簡單,一個數字的狀態只有三種,分別爲不存在,只有一個,有重複。所以,咱們只須要2bits就能夠對一個數字的狀態進行存儲了,假設咱們設定一個數字不存在爲00,存在一次01,存在兩次及其以上爲11。那咱們大概須要存儲空間2G左右。
接下來的任務就是把這20億個數字放進去(存儲),若是對應的狀態位爲00,則將其變爲01,表示存在一次;若是對應的狀態位爲01,則將其變爲11,表示已經有一個了,即出現屢次;若是爲11,則對應的狀態位保持不變,仍表示出現屢次。
最後,統計狀態位爲01的個數,就獲得了不重複的數字個數,時間複雜度爲O(n)。
這就是咱們前面所說的了,int數組中的一個元素是4字節佔32位,那麼除以32就知道元素的下標,對32求餘數(%32)就知道它在哪一位,若是該位是1,則表示存在。
Bitmap主要用於快速檢索關鍵字狀態,一般要求關鍵字是一個連續的序列(或者關鍵字是一個連續序列中的大部分), 最基本的狀況,使用1bit表示一個關鍵字的狀態(可標示兩種狀態),但根據須要也可使用2bit(表示4種狀態),3bit(表示8種狀態)。
Bitmap的主要應用場合:表示連續(或接近連續,即大部分會出現)的關鍵字序列的狀態(狀態數/關鍵字個數 越小越好)。
32位機器上,對於一個整型數,好比int a=1 在內存中佔32bit位,這是爲了方便計算機的運算。可是對於某些應用場景而言,這屬於一種巨大的浪費,由於咱們能夠用對應的32bit位對應存儲十進制的0-31個數,而這就是Bit-map的基本思想。Bit-map算法利用這種思想處理大量數據的排序、查詢以及去重。
在數字沒有溢出的前提下,對於正數和負數,左移一位都至關於乘以2的1次方,左移n位就至關於乘以2的n次方,右移一位至關於除2,右移n位至關於除以2的n次方。
<< 左移,至關於乘以2的n次方,例如:1<<6 至關於1×64=64,3<<4 至關於3×16=48
\>> 右移,至關於除以2的n次方,例如:64>>3 至關於64÷8=8
^ 異或,至關於求餘數,例如:48^32 至關於 48%32=16
不使用第三方變量,交換兩個變量的值
`// 方式一` `a = a + b;` `b = a - b;` `a = a - b;` `// 方式二` `a = a ^ b;` `b = a ^ b;` `a = a ^ b;`
BitSet實現了一個位向量,它能夠根據須要增加。每一位都有一個布爾值。一個BitSet的位能夠被非負整數索引(PS:意思就是每一位均可以表示一個非負整數)。能夠查找、設置、清除某一位。經過邏輯運算符能夠修改另外一個BitSet的內容。默認狀況下,全部的位都有一個默認值false。
圖片
圖片
圖片
圖片
圖片
能夠看到,跟咱們前面想的差很少
用一個long數組來存儲,初始長度64,set值的時候首先右移6位(至關於除以64)計算在數組的什麼位置,而後更改狀態位
別的看不懂沒關係,看懂這兩句就夠了:
`int wordIndex = wordIndex(bitIndex);` `words[wordIndex] |= (1L << bitIndex);`
圖片
Bloom filter 是一個數據結構,它能夠用來判斷某個元素是否在集合內,具備運行快速,內存佔用小的特色。
而高效插入和查詢的代價就是,Bloom Filter 是一個基於機率的數據結構:它只能告訴咱們一個元素絕對不在集合內或可能在集合內。
Bloom filter 的基礎數據結構是一個 比特向量(可理解爲數組)。
主要應用於大規模數據下不須要精確過濾的場景,如檢查垃圾郵件地址,爬蟲URL地址去重,解決緩存穿透問題等
若是想判斷一個元素是否是在一個集合裏,通常想到的是將集合中全部元素保存起來,而後經過比較肯定。鏈表、樹、散列表(哈希表)等等數據結構都是這種思路,可是隨着集合中元素的增長,須要的存儲空間愈來愈大;同時檢索速度也愈來愈慢,檢索時間複雜度分別是O(n)、O(log n)、O(1)。
布隆過濾器的原理是,當一個元素被加入集合時,經過 K 個散列函數將這個元素映射成一個位數組(Bit array)中的 K 個點,把它們置爲 1 。檢索時,只要看看這些點是否是都是1就知道元素是否在集合中;若是這些點有任何一個 0,則被檢元素必定不在;若是都是1,則被檢元素極可能在(之因此說「可能」是偏差的存在)。
一、 首先須要 k 個 hash 函數,每一個函數能夠把 key 散列成爲 1 個整數;
二、初始化時,須要一個長度爲 n 比特的數組,每一個比特位初始化爲 0;
三、某個 key 加入集合時,用 k 個 hash 函數計算出 k 個散列值,並把數組中對應的比特位置爲 1;
四、判斷某個 key 是否在集合時,用 k 個 hash 函數計算出 k 個散列值,並查詢數組中對應的比特位,若是全部的比特位都是1,認爲在集合中。
圖片
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.1-jre</version> </dependency>