天天看點

BitMap核心算法詳解及實作什麼是Bitmap重要特性基本思想MAP映射代碼實作

什麼是Bitmap

所謂的BitMap就是用一個bit位來标記某個元素所對應的value,而key即是該元素,由于BitMap使用了bit位來存儲資料,是以可以大大節省存儲空間。

重要特性

1.存儲空間小

由于Bitmap是通過bit來辨別狀态,資料将會高度壓縮是以占用存儲空間極小,存儲變小也可以帶來很多其它性能紅利,如:記憶體、磁盤IO、網絡帶寬等。

如果用int類型表示一個整數,占用空間為(4byte = 32bit)。若用bitmap表示一個整數,占用空間為1bit。 記憶體節省了32倍。假設需要對10億個整數進行排序(10億個資料不重複,且範圍在0 ~ 2^32)

如果用int類型表示需要占用空間:3.72G 左右

如果用bitmap類型表示需要占用空間:510MB 左右

如果bitmap加上對應的壓縮算法roaringbitmap: 360MB 左右

2.計算速度快

由于BitMap是通過bit位來表示的,是以後面的計算都是基于位運算的,速度都會得到大幅度提升。

基本思想

我們用一個具體例子來說明Bitmap的原理。假設我們要對0-7内的8個元素中的(6,2,7,1)四個元素進行排序(這裡假設元素沒有重複)。我們可以使用Bitmap算法達到排序目的。

1.開辟1 Byte空間(要表示8個數,我們需要8個bit(1Byte)),将這些空間所有的bit都設定為0,如下圖:

BitMap核心算法詳解及實作什麼是Bitmap重要特性基本思想MAP映射代碼實作

2.然後依次将四個元素根據Value的值将對應的bit位置設定為 1,最終8個bit位狀态如下圖:

BitMap核心算法詳解及實作什麼是Bitmap重要特性基本思想MAP映射代碼實作

3.現在我們周遊一次,把值為1的bit的位置輸出(1,2,6,7),這樣便達到了排序的目的。

從上面的例子我們可以看出,BitMap算法的思想還是比較簡單的,關鍵的問題是如何确定10進制的數到2進制的映射圖,接下來我們研究一下map映射。

注意:由于BitMap是用角标來記錄對應的值,而角标是從0開始計數的,是以普通的BitMap隻适用于非負數的排序或者去重操作。

MAP映射

BitMap中1bit代表一個數字,1個int = 4Bytes = 4*8bit = 32 bit,假設現有N個非負整數,取值範圍[0,N-1],那麼N個數需要N/32 int空間,其中:a[0]在記憶體中占32為可以對應十進制數0-31,依次類推如下圖:

a[0]--------->[0]~[31]  ->bit表示[0000000000000000000000000000000000000]
a[1]--------->[32]~[63] ->bit表示[0000000000000000000000000000000000000]
a[2]--------->[64]~[95] ->bit表示[0000000000000000000000000000000000000]
.
.
.
.
.
a[n]--------->[N-32]~[N-1]

說明:n = (N/32) - 1
           

注意:這裡需要提醒一下,a數組的size是由待排序數中的最大值決定,而不是待排序數個數決定的。例如:待排序數組是[3,1,35],這個數組隻有三個數字,但是在開辟BitMap空間時應該根據最大值35,是以需要開辟2個int空間,這個地方大家可以思考一下。自己在學習過程中,發現網絡上多篇文章在這個地方都是錯誤的,是以特意提醒!

接下來介紹如何用位運算将十進制數轉換為對應的bit位:

1.求十進制數在對應數組a中的下标

十進制數0-31,對應在數組a[0]中,32-63對應在數組a[1]中,64-95對應在數組a[2]中………,使用數學歸納分析得出結論:對于一個十進制數n,其在數組a中的下标為:a[n/32]

2.求出十進制數在對應數a[i]中bit位的下标

例如十進制數1在a[0]的bit位下标為1,十進制數31在a[0]的bit位下标為31,十進制數32在a[1]的bit位下标為0。十進制0-31就對應0-31,而32-63則對應也是0-31,即給定一個數n可以通過模32求得在對應數組a[i]中的bit位下标。

3.位移運算

對于一個十進制數n,對應在數組a[n/32][n%32]中,但數組a畢竟不是一個二維數組,我們通過移位操作實作将對應的bit位置 1。

位運算公式:a[n/32] |= 1 << (n % 32),即 a[n/32] = a[n/32] |(1 << (n % 32))

公式還有一個可以優化的地方,n % 32 等價于 n & 0x1F,解釋:(n & 0x1F) 保留n的後五位 相當于 n % 32

至此,整個MAP映射過程結束。需要注意的是,本示例中底層使用的存儲是int[],如果換做其它類型數組,如:long,則對應公式需要做相應調整。

代碼實作

根據上面介紹的算法原理,代碼實作如下:

package com.zhouj.endless.ds;

import java.util.ArrayList;
import java.util.List;

/**
 * @author zhouj
 * @date 2021-07-20 22:28
 * @desc BitMap實作
 */
public class BitMap {
    // 最大值
    private static final int N = Integer.MAX_VALUE;
    // [0,2147483647]
    private int[] a = new int[N / 32 + 1];

    /**
     * 設定bit位為1
     *
     * @param n
     */
    public void setBit(int n) {
        //row = n / 32 求十進制數在數組a中的下标
        int row = n >> 5;
        //相當于 n % 32 求十進制數在數組a[i]中的下标
        a[row] |= 1 << (n & 0x1F);
    }

    // 判斷所在的bit為是否為1
    public boolean exits(int n) {
        int row = n >> 5;
        return (a[row] & (1 << (n & 0x1F))) != 0;
    }

    /**
     * 展示前row個數組
     *
     * @param row
     */
    public void display(int row) {
        System.out.println("BitMap位圖展示前N組");
        for (int i = 0; i < row; i++) {
            List<Integer> list = new ArrayList<>();
            int temp = a[i];
            for (int j = 0; j < 32; j++) {
                list.add(temp & 1);
                temp >>= 1;
            }
            System.out.println("a[" + i + "]--------->" + 32 * i + "~" + (32 * (i + 1) - 1) + "->bit表示" + list);

        }
    }

    /**
     * 展示第N個數組
     *
     * @param n
     */
    public void displayN(int n) {
        System.out.println("BitMap位圖展示第N組");
        List<Integer> list = new ArrayList<>();
        int temp = a[n - 1];
        for (int j = 0; j < 32; j++) {
            list.add(temp & 1);
            temp >>= 1;
        }
        System.out.println("a[" + (n - 1) + "]--------->" + 32 * (n - 1) + "~" + (32 * n - 1) + "->bit表示" + list);


    }

    public static void main(String[] args) {
        int num[] = {1, 5, 30, 31, 64, 56, 159, 120, 21, 17, 35, 45, Integer.MAX_VALUE};
        BitMap map = new BitMap();
        for (int i = 0; i < num.length; i++) {
            map.setBit(num[i]);
        }
        map.display(6);

        int temp = Integer.MAX_VALUE;
        if (map.exits(temp)) {
            System.out.println("temp:" + temp + " exists");
        }
        map.displayN(Integer.MAX_VALUE / 32 + 1);
    }
}
           

運作結果如下:

BitMap核心算法詳解及實作什麼是Bitmap重要特性基本思想MAP映射代碼實作

本文給出的隻是BitMap原來簡單實作,在實際應用中有更複雜的情況。JDK中,是直接提供的BitMap集合的實作類的:java.util.BitSet,有興趣的可以去研究一下。另外介于篇幅原因,針對BitMap的應用将在下一篇文章進行介紹和分析。

感謝閱讀!