1. 程式人生 > 其它 >用什麼演算法可以快速檢索使用者資料?Bitmap瞭解一下!

用什麼演算法可以快速檢索使用者資料?Bitmap瞭解一下!

作者:沈熠輝

來源:恆生LIGHT雲社群

一個關於使用者標籤的需求

為了幫助公司精準定位使用者群體,咱們需要開發一個使用者畫像系統,實現使用者資訊的標籤化。

使用者標籤包括使用者的社會屬性、生活習慣、消費行為等資訊,例如下面這個樣子。

通過使用者標籤,我們可以對多樣的使用者群體進行統計。例如統計使用者的男女比例、統計喜歡旅遊的使用者數量等。

為了滿足使用者標籤的統計需求,小灰利用關係資料庫設計瞭如下的表結構,每一個維度的標籤對應著資料庫表中的一列:

要想統計所有“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))