Algorithms_算法专项_Bitmap原理及应用
文章目錄
- 引導案例
- 基礎知識回顧
- Bitmap
- 計算不同存儲類型需要開辟的內存空間
- 原理
- 計算需要開辟多大長度的數組
- 找到目標數字在數組中的下標
- 找到目標數字在對應數組下標中的位置
- 總結下3個步驟
- 存
- 判斷是否存在
- 刪除
- 代碼
- 測試
- bitmap優缺點總結
- 優點
- 缺點
- 小試牛刀
引導案例
Q: 如何在3億個整數, 每個整數的范圍是 0到2億,判斷一個數是否存在于3億個整數中。 要求內存使用在100M以內,一臺主機。
…
思考下…
數組? hash ? 分治? bitmap ? 布隆過濾器 ?
用數組的話 , 開辟個data[2億]長度的數組, data[1]=1 表示存在,data[2]=0表示不存在。 理論上可行,但是內存肯定超過了500M, 不可行
用hash的話,開3億個空間, 舉個例子 使用HashMap --> put(1,true) , get(1)為true,則存在, 聽起來也可行哈,但是3億個int類型 內存也是絕對會超過500M的。
分治: 內存要求也不滿足
bitmap ? 布隆過濾器? 這兩個是可以的
我們這里先用bitmap來解決這個問題
基礎知識回顧
Algorithms_數據結構與算法_位運算符的巧技一二事
Bitmap
計算不同存儲類型需要開辟的內存空間
在計算機科學中,bit通常用于表示信息的最小單位,也可以被稱作是二進制位。
在計算機學科中,bit一般用0和1表示。
Byte也就是人們常說的字節,通常由8個位(8bit)組成一個字節(1Byte)
比如我們常見的基本類型的取值范圍
bit與Byte之間可以進行換算,具體的換算關系為:1Byte=8bit
計算下3億數據需要開辟的內存空間:
-
如果使用 int數組來存儲, 那就需要3億個int , 1個int占用4個Byte字節 , 故3億個int 占用的內存空間為 3億 * 4 (總共占用的Byte) / 1024 (轉為KB) / 1024 (轉為MB) = 1144 MB
-
如果使用 bit數組來存儲,那么同樣也需要3億個bit, 8個bit才等于 1個Byte字節, 故3億個bit 占用內存空間為 3億/8 (總共占用的Byte) / 1024 (轉為KB) / 1024 (轉為MB) = 35.7 MB
-
當然了 你也可以用long 或者 short來存儲,無非就是計算的時候的轉換關系調整下 。 如果使用 long 數組來存儲, 那就需要3億個long , 1個long 占用8個Byte字節 , 故3億個long占用的內存空間為 3億 * 8 (總共占用的Byte) / 1024 (轉為KB) / 1024 (轉為MB) = 2288 MB 。
-
如果使用 short數組來存儲, 那就需要3億個short, 1個short占用2個Byte字節 , 故3億個short占用的內存空間為 3億 * 2(總共占用的Byte) / 1024 (轉為KB) / 1024 (轉為MB) = 572MB 。
-
其他類型也可以,主要是需要開辟的內存大小,當然是越小越好了…
結論顯而易見,使用二進制bit來存儲 3億個數,比使用int來存儲3億個數 需要開辟的內存空間從1個G 降低為 36M, 少了30倍 ,你說它不香嗎???
原理
bitmap就是利用一個 bit 二進制位(0,1)來存儲。由于采用bit為單位來存儲數據,可以大大的節省存儲空間。
OK , 計算完了需要開辟的內存空間,如果使用bit來存儲這些數據,該如何表示呢?
我們知道bit 二進制位,在計算機學科中,bit一般用0和1表示。
每一個bit位表示一個數,0表示不存在,1表示存在,這正符合bit二進制。 舉個例子我們要存儲 {1,2,4,6}
我們需要用bit來 存儲,java.util包中有個專門存儲bit的類 BitSet
當然了,我們也可以用其他類型來表示bit . 接下來我們以int為例 (當然了 你用byte , short ,long 都可以…)
計算需要開辟多大長度的數組
比如我們使用int來表示bit , 1個int = 4 個Byte = 4 * 8 bit . 因此一個int可以存儲32個int
假設有 65個數字(0~64),最大數字為64 。 用bit表示,肯定需要64bit ,如果用int來表示,需要幾個int呢 ?
我們來計算下
-----> 需要3個int,才能把這65個bit 存下來。
-----> 65個數字 需要3個int (64/32 +1) , 97個數字 需要4個int (96/32+1) … 【從0開始計算,最大值 64 ,96】
-----> 推到出來一個公式來根據存儲的bit數量計算(取數據的最大值)需要幾個int
故需要開辟的int數組的容量為 (MAX/32 + 1) , 一定要注意的是 一定要取MAX來計算。 至于為啥子除以32 , 1 int = 4 Byte = 4 * 8 bit ,通過轉換計算來的。 看到MAX/32 這種 ,應該馬上想到 右移 2^5次方 這種高效的計算方式。 即 (MAX >> 5 + 1 )
找到目標數字在數組中的下標
確定了需要開辟幾個int數組,假設我們要把64這個值存儲到bit中(當然了,存到bit里肯定不是64 ,bit二進制位 存0 1。 1 表示存在,0 表示不存在)
是不是得找到這個具體的位置 ?
----> 是不是需要先定位到數組中的哪個int , 比如 64 在 int[2],先找到 int[2]
----> 64~95 在 int[2] , 32~63 在int[1] , 96-127 在int[4]
—> 推到出來一個公式來 找到目標數字在數組中的下標 n / 32 ,同樣的 ,通過位移的方式更高效 n/32 —> n/2^5 --> n >> 5
找到目標數字在對應數組下標中的位置
----> 然后再看下 這個int存儲了32位,那我這個目標值應該在第幾個位置 ,顯而易見,我們的64在第一個位置。
----> 64 在 int[2] 的 第1位,即下標為0的位置, 65呢 下標為1的位置 ,70 呢 下標為6
—> 推到出來一個公式來 找到目標數字在在對應數組下標中的位置 ,取余即可 n %32 ,(因為除數32位2^5)所以 ,通過位移的方式更高效 n%32 —> n%2^5 --> n &(2^5-1) —> n & 31
除數為2的n次方時,使用位操作(&運算)代替求余操作
總結下3個步驟
1. 計算需要開辟多大長度的數組 :(MAX >> 5 + 1 )
2. 找到目標數字在數組中的下標 : n >> 5
3. 找到目標數字在對應數組下標中的位置 : n & 31
注意: 計算需要開辟多大長度的數組,一定要取最大值計算,假設你要存3億個數據
—> 根據公式 需要開辟的數組長度為: 3億/32 + 1 = 9375001(9百30多萬) 。
(1個int 占4個字節,9375001 * 4 (總字節大小) /1024 /1024 = 35.76MB)
存
存的本質就是將下標為pos的位由0變為1---->那就把1左移pos位,然后使用或運算符運算即可 (按位或運算【|】有一個為1時,結果位就為1)
舉個例子 pos = 2 , pos 2原本的bit值是 0 (其實不管是0 ,還是1)
–> 0 | 1 --> 1
–> 1 | 1 --> 1
判斷是否存在
public boolean find(int n) {// bitMap構建的時候,根據max來構建的,如果入參n>max,直接返回不存在if (n > max) return false;return (bitsArray[index(n)] &= 1<<position(n)) != 0;}假設黃色的二進制位1存儲的是數字n,要想判斷n是否存在
第一步: 找到n所在的數組下標
第二步: 在第一步確定的那個數組下標對應的int里(假設我們是用int來存儲bit,32個bit), 確定這個n具體在哪個position,
第三步:把1 左移 position個位置,然后和目標的二進制bit位,進行 與運算. 只有都為1,才說明存在。 如果目標的二進制bit位是0,0&1=0 ,說明這個位置沒有被add過,也就不存在。
注: add的時候,用的是bitsArray[index(n)] |= 1 << position(n) ,所以add完成以后bitsArray[index(n)]就有值了,所以這里繼續使用bitsArray[index(n)]進行 &運算
刪除
刪除無非就是把 1置為 0 。
將1左移position后,因為存在,那個位置自然就是1,然后取反就是0,再與當前值做&,即可清除當前的位置了
/*** * @param n* @return*/public boolean del(int n) {// 不存在 返回刪除失敗if (!find(n)) return false;// 將1左移position后,因為存在,那個位置自然就是1,然后取反就是0,// 再與當前值做&,即可清除當前的位置了.return (bitsArray[index(n)] &= ~(1<<position(n))) == 0 ;}代碼
/*** @author 小工匠* @version v1.0* @create 2019-12-20 6:47* @motto show me the code ,change the word* @blog https://artisan.blog.csdn.net/* @description**/public class ArtisanBitMap {// 這批數據的最大值,用于確定需要開辟的數組長度private int max;// 數據映射到bit位上,這里用int表示的bit ,1個int占4個字節即4*1Byte = 4*8bit = 32bitprivate int[] bitsArray;/*** 構造函數** @param max*/public ArtisanBitMap(int max) {this.max = max;// 構建int數組,用于存儲bit// (max >> 5) 一定要加括號,因為 +號的優先級比>>高bitsArray = new int[(max >> 5) + 1];}/*** 初始化數據** 其實用啥和1進行或運算都行,這里使用bitsArray[index(n)]* @return*/public boolean add(int n) {// 或運算return (bitsArray[index(n)] |= 1 << position(n)) != 0;}public boolean find(int n) {// bitMap構建的時候,根據max來構建的,如果入參n>max,直接返回不存在if (n > max) return false;return (bitsArray[index(n)] &= 1<<position(n)) != 0;}/**** @param n* @return*/public boolean del(int n) {// 不存在 返回刪除失敗if (!find(n)) return false;// 將1左移position后,因為存在,那個位置自然就是1,然后取反就是0,// 再與當前值做&,即可清除當前的位置了.return (bitsArray[index(n)] &= ~(1<<position(n))) == 0 ;}/*** 根據n 判斷n所在的數組下標** @param n* @return*/public int index(int n) {// n/32 --> n 除以 32 (2^5) ,可以表示為 n向右移5位return n >> 5;}/*** 根據n 判斷n所在數組下標對應的存儲位置* 即:數組中的一個int可以存儲32位,判斷這個n需要存儲在哪一位** @param n* @return*/public int position(int n) {// n%32 --> n 取余32 (2^5) ,可以表示為 n &(2^5 -1)return n & 31;}public static void main(String[] args) {// 最大是65ArtisanBitMap bitMap = new ArtisanBitMap(300000000);bitMap.add(2);bitMap.add(300000000);// 判斷是否存在boolean a = bitMap.find(2);System.out.println("2是否存在: " + a);boolean b = bitMap.find(300000000);System.out.println("300000000是否存在: " + b);// 刪除操作boolean delete = bitMap.del(2);System.out.println("2是否刪除成功: " + delete);System.out.println("2是否存在: " + bitMap.find(2));}}測試
bitmap優缺點總結
優點
缺點
所以針對存在的缺點,就要布隆過濾器這個神器了,下篇我們探討下布隆過濾器的原理及使用。
小試牛刀
Q: 某個超大的文件中包含一些手機號碼,每個號碼為11位數字,求不同號碼的個數。
有了上面的處理思路,是不是這個問題也有思路了呢?
每個號碼11位, 最大值也就是 11個9唄…so , 懂了吧
總結
以上是生活随笔為你收集整理的Algorithms_算法专项_Bitmap原理及应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Algorithms_算法专项_Hash
- 下一篇: Algorithms_基础数据结构(01