bitmap原理
bitmap是什么?在計算機中一個字節(byte)=8位(bit),這里的bit就是位,數據的最小表示單位,map一般是表示地圖或者映射。
簡單回顧一下二進制的一些知識:
1byte=8bit
1個bit有二種狀態:0或1
所以1個byte可以表示00000000->11111111,也就是十進制0-255
其中十進制和二進制的對應關系如下:
0 ---------> 0000 0000 1 ---------> 0000 0001 2 ---------> 0000 0010 3 ---------> 0000 0011 4 ---------> 0000 0100 5 ---------> 0000 0101 6 ---------> 0000 0110 7 ---------> 0000 0111 8 ---------> 0000 1000 9 ---------> 0000 1001 10 ---------> 0000 1010 11 ---------> 0000 1011 12 ---------> 0000 1100 13 ---------> 0000 1101 14 ---------> 0000 1110 15 ---------> 0000 1111 ....................... ....................... 255...........1111 1111
在大部分變成語言里,int類型一般的都是占4個byte,也就是32位,甭管你這個數字是1或者是21億你都得棧32位,所以如果你現在有10億數字需要存在內存里面,需要分配多少內存呢:
1000000000 * 4 / 1024 / 1024 = 3800MB,大概需要3800MB內存,這里計算出的數值只適合C,在PHP里面,一個整形變量占用的實際空間遠遠大於4byte,是好幾倍。
為了解決這個問題,bitmap采用一種映射機制,舉個例子,假如有1、3、7、2、5這5個數字需要存放,正常情況下需要5*4bye=20byte,但是bitmap只需要1byte,它是如何做到的?
假設下面是1byte,首先將所有位置為0:00000000
從第一個0開始數數,把對應數字的位置置位1,比如說第一個1那就是第2個位置置位1,第二個三就是把第4個位置置位1,以此類推
1 => 0 1 0 0 0 0 0 0 3 => 0 0 0 1 0 0 0 0 7 => 0 0 0 0 0 0 0 1 2 => 0 0 1 0 0 0 0 0 5 => 0 0 0 0 0 1 0 0
累加起來最終的串就是:0 1 1 1 0 1 0 1
其實最終的數字和二進制沒什么關系,純粹是數數,這個串就代表最大到7的數字,然后我們就開始數數,從0開始:
比如第1個位置是1,那就記個1
比如第2個位置是1,那就記個2
比如第3個位置是1,那就記個3
比如第5個位置是1,那就記個5
比如第7個位置是1,那就記個7
結果就是1 2 3 5 7,不僅僅排序,而且還排重了,如果按照這種轉換機制,1個int類型,32位的話,可以表示0-31之間的數字
如果要表示最大一萬的數,就需要1萬個位的串,但是編程語言並沒有這樣的數據類型,但是可以用數組去模擬。舉個例子,一個整形是32位,也就說我們大概需要314個數組元素來表示這個串:
- 數組第1個元素 00 - 31
- 數組第2個元素 32 - 63
- 數組第3個元素 64 - 95
- 數組第4個元素 96 - 127
- ……
提到這個算法的好處,最大的好處就是節省內存,節省了好幾十倍,適合處理大量數據,除了快速排序,還可以快速去重,快速查詢是否存在,還有一個好聽的應用Bloom Filter(布隆過濾器)
redis bitmap應用
setBit
說明:給一個指定的key的值的第offset位賦值位value
參數:key offset value:bool or int (1 or 0)
返回值:long:0或1
getBit
說明:返回一個指定key的二進制信息
參數:key offset
返回值:long
bitCount
說明:返回一個指定key中位的值為1的個數(是以byte為單位的bit)
參數:key start offset
返回值:long
bitOp
說明:對不同的二進制存儲數據進行位運算(AND、OR、NOT、XOR)
參數:operation destkey key [key …]
返回值:long
bitmap優勢:
- 基於最小的單位bit進行存儲,所以非常省空間
- 設置時候時間復雜度O(1),讀取時候時間復雜度O(n),操作時非常快的
- 二進制數據的存儲,進行相關計算的時候非常快
- 方便擴容
bitmap限制:
redis中bit映射並限制在512MB之內,所以最大時2^32位。建議每個key的位數都控制下,因為讀取時候時間復雜度O(n),越大的串讀的時間花銷越大。
bitmap使用場景:
1.視頻屬性的無限延伸:
需求分析:一個擁有億級數據量的短視頻app,視頻存在各種屬性(是否加鎖、是否特效等等),需要做各種標記。
可能想到的解決方案:
- 存儲在mysql中,肯定不行,一個是隨着業務增長屬性一直增加,並且存在有時間限制的屬性,直接對數據庫進行加減字段是非常不合理的做法。即使是存在一個字段中用json等壓縮技術存儲也存在讀效率的問題,並且對於大幾億的數據來說,廢棄的字段回收起來非常麻煩。
- 直接記錄在redis中,根據業務屬性+uid為key來存儲。讀寫效率角度沒毛病,但是存儲的角度來說key的數據量都大於value了,太耗費空間了。即使是用json等壓縮技術來存儲。也存在問題,解壓需要時間,並且大幾億的數據回收也是難題。
設計方案:
使用redis的bitmap進行存儲。
key由屬性id+視頻分片id組成。value按照視頻id對分片范圍取模來決定偏移量offset。10億視頻一個屬性約120m還是挺划算的。
偽代碼:
2.用戶在線狀態
需求分析:需要對子項目提供一個接口,來提供某用戶是否在線?
設計方案:使用bitmap是一個節約空間效率又高的一種方法,只需要一個key,然后用戶id為偏移量offset,如果在線就設置為1,不在線就設置為0,3億用戶只需要36MB的空間。
偽代碼:
3.統計活躍用戶
需求分析:需要計算活躍用戶的數據情況。
設計方案:使用時間作為緩存的key,然后用戶id為offset,如果當日活躍過就設置為1。之后通過bitOp進行二進制計算算出在某段時間內用戶的活躍情況。
偽代碼:
4.用戶簽到
需求分析:用戶需要進行簽到,對於簽到的數據需要進行分析與相應的運運營策略。
設計方案:使用redis的bitmap,由於是長尾的記錄,所以key主要由uid組成,設定一個初始時間,往后沒加一天即對應value中的offset的位置。
偽代碼:
原文出處: