BitMap算法及實現點贊功能

BitMap簡介

bitmap聽起來是位圖的意思,其實就一種基於位的映射,bitmap是一個十分有用的結構。所謂的Bit-map就是用一個bit位來標記某個元素對應的Value, 而Key即是該元素。由於採用了Bit爲單位來存儲數據,因此可以大大節省存儲空間。

爲什麼要使用bitmap?
舉個例子,有一個無序有界int數組{1,2,5,7},初步估計佔用內存44=16字節,這倒是沒什麼奇怪的;但是假如有10億個這樣的數呢,10億4/(102410241024)=3.72G左右。如果這樣的一個大的數據做查找和排序,那估計內存也崩潰了,有人說,這些數據可以不用一次性加載,那就是要存盤了,存盤必然消耗IO。

如果用BitMap思想來解決的話,就好很多。一個byte是佔8個bit,如果每一個bit的值就是有或者沒有,也就是二進制的0或者1,如果用bit的位置代表數組值有還是沒有,那麼0代表該數值沒有出現過,1代表該數組值出現過。也可以描述數據。具體如下圖:
在這裏插入圖片描述
現在假如10億的數據所需的空間就是3.72G/32,一個佔用32bit的數據現在只佔用了1bit,節省了不少的空間,排序就更不用說了,一切顯得那麼順利。這樣的數據之間沒有關聯性,要是讀取的,你可以用多線程的方式去讀取。時間複雜度方面也是O(Max/n),其中Max爲byte[]數組的大小,n爲線程大小。

BitMap的映射

我們使用java的int類型(總共32位)來作爲基本類型,一個int能夠對應32個數(一位對應一個數),然後組成一個int類型的數組,長度爲n,總共能對應32*n個數

假設需要排序或者查找的最大數MAX=10000000(lz:這裏MAX應該是最大的數而不是int數據的總數!),那麼我們需要申請內存空間的大小爲int a[1 + MAX/32]。

Java實現

內部元素
	/**
	 * bitMap中可以加入的最大數字(範圍是從0到MAX_VALUE)
	 */
	public static final int MAX_VALUE=10000;
	
	/**
	 * 存放bitmap的數組,每個int有32位,對應32個數字
	 */
	private int[] a=new int[MAX_VALUE/32+1];
加入
	/**在bitmap中加入元素n
	 * @param n 範圍爲[0,MAX_VALUE]
	 */
	public void addValue(int n){
		if(n<0||n>MAX_VALUE){
			System.out.println("不再0到"+MAX_VALUE+"的範圍內,不能加入");
			return;
		}
		//n對應數組的哪個元素,是n/32
		int row=n>>5;
		//n對應的int中的位置,是n mod 32
		int index=n & 0x1F;
		//在n對應的int,對應的位置,置1
		a[row] |=1<<index;				
	}
查找
	/**查找bitmap中是否有元素n
	 * @param n
	 * @return 如果存在,返回true  不存在,返回false
	 */
	public boolean existValue(int n){
		if(n<0||n>MAX_VALUE){
			System.out.println("不再0到"+MAX_VALUE+"的範圍內,一定沒有");
			return false;
		}
		//n對應數組的哪個元素,是n/32
		int row=n>>5;
		//n對應的int中的位置,是n mod 32
		int index=n & 0x1F;
		//result爲哪個位置上現在保存的值(爲10000(index個0)或者0)
		int result=a[row] & (1<<index);
		//如果不爲0,則那個位置一定爲1
		return result!=0;
		
	}
刪除
	/**在bitmap中刪除元素n
	 * @param n
	 */
	public void removeValue(int n){
		if(n<0||n>MAX_VALUE){
			System.out.println("不再0到"+MAX_VALUE+"的範圍內,一定沒有");
			return;
		}
		//n對應數組的哪個元素,是n/32
		int row=n>>5;
		//n對應的int中的位置,是n mod 32
		int index=n & 0x1F;
		//對應位置0,與 111101111進行與運算,那位一定變0
		a[row] &=~(1<<index);
	}
展示
	
	/** 展示第row行的情況,元素的二進制情況,和有的元素
	 * @param row
	 */
	public void displayRow(int row){
		System.out.print("bitmap展示第"+row+"行:"+Integer.toBinaryString(a[row])+" 有:");
		//對應row:32*row到32*row+31
		int now=row<<5;
		//temp爲與對應位進行與運算的數字
		int temp=1;
		for(int i=0;i<32;i++){
			int result=a[row] & temp;
			if(result!=0){
				System.out.print("  "+now+"  ");
			}
			now++;
			temp=temp<<1;
		}
		System.out.println();
		
	}
測試
package datastructure.bitmap;
 
public class Main {
 
	public static void main(String[] args) {
 
		BitMap bitMap=new BitMap();
		bitMap.addValue(0);
		bitMap.addValue(31);
		bitMap.displayRow(0);
		System.out.println(bitMap.existValue(1));
		System.out.println(bitMap.existValue(31));
		bitMap.removeValue(0);
		System.out.println(bitMap.existValue(0));
		bitMap.displayRow(0);
		
		bitMap.addValue(34);
		bitMap.displayRow(1);
	}
 
}=

Redis使用BitMap實現點贊功能

redis不僅能存儲String,Hash,List,set,zset這幾週數據類型,還能存儲,bitmap,geo,hyperloglog,這裏我們使用bitmap來作爲點讚的存儲結構

點贊/取消點贊

假設用戶的數字id爲1000,對照片id爲100的照片點贊。首先根據照片id生成贊數據存儲的redis key,比如生成策略爲like_photo:{photo_id},id爲1000的用戶點贊,只需要將like_photo:100的第1000位置爲1即可(取消贊則置爲0)。

redis setbit操作的時間複雜度爲O(1),所以這種點贊方式十分高效。

redis.setbit("like_photo:100", 1000, 1);
當前是否點贊

用戶打開圖片的時候需要查詢當前是否點贊過該照片,查詢是否點贊可以通過redis getbit操作來實現。比如查詢用戶id爲1000的用戶是否點贊過照片id爲100的照片,只需要對like_photo:100bitmap的第1000位取值即可。

redis getbit操作的時間複雜度同樣是O(1)。

redis.getbit("like_photo:100", 1000);
查詢點贊總次數

比如需要顯示照片id爲100的照片的獲贊次數,只需要對like_photo:100bitmap進行位圖計數操作即可。

redis bitcount操作的時間複雜度雖然是O(N)的,但是大部分數據量的情況下是不需要擔心bitcount效率問題的

redis.bitcount("like_photo:100");
bittop

比如要計算同時點讚了100和101兩張照片的用戶,可以通過如下操作實現

redis.bitop("AND", "like_photo:100&101", "like_photo:100", "like_photo:101");

得到的like_photo:100&101這個臨時key中即是同時點贊100和101的用戶bitmap.

侷限性

這種方案雖然比較高效,實現起來也比較簡單,但是也有一定的侷限性。
1.需要用戶有類似於數據庫自增id的數字id,當然如果你是從10000之類的開始自增的,在bitmap操作的時候可以統一將用戶id減掉10000,這樣可以稍微節省一些redis內存佔用;
2.當用戶量很大的時候,比如千萬級用戶量的情況下,一個用戶的bitmap需要消耗的內存爲:10000000/8/1024/1024=1.19MB,當bitmap數量較多的時候,內存佔用還是很可觀的。不過在用戶量較少的時候這種方案還是不錯的
我這裏測試使用八位數千萬級別的id=28000000設置到bitmap裏面,然後dump出了redis的rdb文件如下:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章