用什麼演算法可以快速檢索使用者資料?Bitmap瞭解一下!
來源:
一個關於使用者標籤的需求
為了幫助公司精準定位使用者群體,咱們需要開發一個使用者畫像系統,實現使用者資訊的標籤化。
使用者標籤包括使用者的社會屬性、生活習慣、消費行為等資訊,例如下面這個樣子。
通過使用者標籤,我們可以對多樣的使用者群體進行統計。例如統計使用者的男女比例、統計喜歡旅遊的使用者數量等。
為了滿足使用者標籤的統計需求,小灰利用關係資料庫設計瞭如下的表結構,每一個維度的標籤對應著資料庫表中的一列:
要想統計所有“90後”的程式設計師,該怎麼做呢?
用一條求交集的SQL語句即可。
看起來很簡單嘛,嘿嘿……
事情沒那麼簡單,現在標籤越來越多,例如,使用者去過的城市、消費水平、愛吃的東西、喜歡的音樂……都快有上千個標籤了,這要給資料庫表增加多少列啊!
篩選的標籤條件過多的時候,拼出來的SQL語句像麵條一樣長……
不僅如此,當對多個使用者群體求並集時,需要用distinct來去掉重複資料,效能實在太差了……
用BITMAP演算法解決問題
你聽說過Bitmap演算法嗎?在中文裡叫作點陣圖演算法。
這裡所說的點陣圖並不是畫素圖片的點陣圖,而是記憶體中連續的二進位制位(bit)所組成的資料結構,該演算法主要用於對大量整數做去重和查詢操作。
舉一個例子,假設給出一塊長度為10bit的記憶體空間,也就是Bitmap,想要依次插入整數4、2、1、3,需要怎麼做呢?
很簡單,具體做法如下。
第1步,給出一塊長度為10的Bitmap,其中的每一個bit位分別對應著從0到9的整型數。此時,Bitmap的所有位都是0(用紫色表示)。
第2步,把整型數4存入Bitmap,對應儲存的位置就是下標為4的位置,將此bit設定為1(用黃色表示)。
第3步,把整型數2存入Bitmap,對應儲存的位置就是下標為2的位置,將此bit設定為1。
第4步,把整型數1存入Bitmap,對應儲存的位置就是下標為1的位置,將此bit設定為1。
第5步,把整型數3存入Bitmap,對應儲存的位置就是下標為3的位置,將此bit設定為1。
如果問此時Bitmap裡儲存了哪些元素,顯然是4、3、2、1,一目瞭然。
Bitmap不僅方便查詢,還可以去掉重複的整數。
你仔細想一想,你所做的使用者標籤能不能用Bitmap的形式進行儲存呢?
我的每一條使用者資料都對應著成百上千個標籤,怎麼也無法轉換成Bitmap的形式啊?
別急,我們不妨轉換一下思路,為什麼一定要讓一個使用者對應多個標籤,而不是一個標籤對應多個使用者呢?
資訊不一定非要以使用者為中心儲存,也能夠以標籤為中心來儲存,讓每一個標籤儲存包含此標籤的所有使用者ID,就像倒排索引一樣!
第1步,建立使用者名稱和使用者ID的對映。
第2步,讓每一個標籤儲存包含此標籤的所有使用者ID,每一個標籤都是一個獨立的Bitmap。
這樣一來,每一個使用者特徵都變得一目瞭然。
例如,程式設計師和“00後”這兩個群體,各自的Bitmap分別如下所示。
BitMap好處
1.高效能的位運算
2.相比使用雜湊表的話,每一個使用者ID都要用整型資料儲存,少則佔用4位元組(32bit),多則佔用8位元組(64bit)。而一個使用者ID在Bitmap中只佔1bit,記憶體是使用雜湊表所佔用記憶體的1/32,甚至更少!
3.Bitmap在對使用者群做交集和並集運算時也有極大的便利
如何取反操作呢
我們可以使用異或 運算進行操作,即相同位為0,不同位為1。
同樣是剛才的例子,我們給出“90後”使用者的Bitmap,再給出一個全量使用者的Bitmap。最終要求出的是存在於全量使用者,但又不存在於“90後”使用者的部分。
實現方式
長度計算公式
int nSize = (width * bitPixel + 64) / 64 ;
(高效寫法是(((width * bitPixel + 64)>>6)) )
通過位移操作,可以很方便的擴容
而且越往上就是指數擴容,滿足過億級別資料量的時間複雜度也是O(1)
class MyBitmap: def __init__(self,size): self.words=[0]*(self.get_word_index(size-1)+1) self.size=size def get_bit(self,bit_index): if bit_index<0 or bit_index>self.size-1: raise Exception("超過Bitmap有效範圍!") word_index=self.get_word_index(bit_index) return (self.words[word_index]&(1<<bit_index))!=0 def set_bit(self,bit_index): if bit_index<0 or bit_index>self.size-1: raise Exception("超過Bitmap有效範圍!") word_index=self.get_word_index(bit_index) self.words[word_index] |=(1<<bit_index) def get_word_index(self,bit_index): #右移6位,相當於除以64 return bit_index>>6 bitMap=MyBitmap(128) bitMap.set_bit(126) bitMap.set_bit(75) print(bitMap.get_bit(126)) print(bitMap.get_bit(78))