Redis-BitMap

 BitMap是什麼

             經過一個bit位來表示某個元素對應的值或者狀態,其中的key就是對應元素自己。Bitmaps 自己不是一種數據結構,實際上它就是字符串(key 對應的 value 就是上圖中最後的一串二進制),可是它能夠對字符串的位進行操做。 Bitmaps 單獨提供了一套命令,因此在 Redis 中使用 Bitmaps 和使用字符串的方法不太相同。能夠把 Bitmaps 想象成一個以 位 爲單位的數組,數組的每一個單元只能存儲 0 和 1,數組的下標在Bitmaps中叫作偏移量。redis


BitMap的命令數組

SETBIT

語法:SETBIT key offset value
bash

說明:

key 所儲存的字符串值,設置或清除指定偏移量上的位(bit)。服務器

位的設置或清除取決於 value 參數,能夠是 0 也能夠是 1數據結構

key 不存在時,自動生成一個新的字符串值。性能

字符串會進行伸展(grown)以確保它能夠將 value 保存在指定的偏移量上。當字符串值進行伸展時,空白位置以 0 填充。測試

offset 參數必須大於或等於 0 ,小於 2^32 (bit 映射被限制在 512 MB 以內)。大數據

對使用大的 offsetSETBIT 操做來講,內存分配可能形成 Redis 服務器被阻塞。ui

返回值:

字符串值指定偏移量上原來儲存的位(bit)。spa

示例:
# SETBIT 會返回以前位的值(默認是 0)這裏會生成 126 個位
coderknock> SETBIT testBit 125 1
(integer) 0
coderknock> SETBIT testBit 125 0
(integer) 1
coderknock> SETBIT testBit 125 1
(integer) 0
coderknock> GETBIT testBit 125
(integer) 1
coderknock> GETBIT testBit 100
(integer) 0
# SETBIT value 只能是 0 或者 1 二進制只能是0或者1
coderknock> SETBIT testBit 618 2
(error) ERR bit is not an integer or out of range複製代碼

獲取值

GETBIT


語法:GETBIT key offset
說明:

key 所儲存的字符串值,獲取指定偏移量上的位(bit)。

offset 比字符串值的長度大,或者 key 不存在時,返回 0

返回值:

字符串值指定偏移量上的位(bit)。

示例:
coderknock> EXISTS bit
(integer) 0
coderknock> SETBIT bit 125 1
(integer) 0
coderknock> GETBIT bit 125
(integer) 1
# 偏移量若是不存在則是0
coderknock> GETBIT bit 126
(integer) 0
# 能夠看到 bit 自己也是個字符串
coderknock> GET bit
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 "複製代碼

獲取Bitmaps 指定範圍值爲 1 的位個數

BITCOUNT


語法:BITCOUNT key start
說明:

計算給定字符串中,被設置爲 1 的比特位的數量。

通常狀況下,給定的整個字符串都會被進行計數,經過指定額外的 startend 參數,可讓計數只在特定的位上進行。

startend 參數的設置和 GETRANGE 命令相似,均可以使用負數值: 好比 -1 表示最後一個字節, -2表示倒數第二個字節,以此類推。

不存在的 key 被當成是空字符串來處理,所以對一個不存在的 key 進行 BITCOUNT 操做,結果爲 0

返回值:

被設置爲 1 的位的數量。

示例:
# 此處的 bit 基於 GETBIT 示例的命令中的
coderknock> BITCOUNT bit
(integer) 1
# 計算 bit 中全部值爲 1 的位的個數
coderknock> BITCOUNT bit
(integer) 1
coderknock> SETBIT bit 0 1
(integer) 0
coderknock> BITCOUNT bit
(integer) 2
# 計算指定位置 bit 中全部值爲 1 的位的個數
coderknock> BITCOUNT bit 10 126
(integer) 1複製代碼

多個 Bitmaps 運算

BITOP


語法:BITOP operation destkey key [key ...]
說明:

對一個或多個保存二進制位的字符串 key 進行位元操做,並將結果保存到 destkey 上。

operation 能夠是 ANDORNOTXOR 這四種操做中的任意一種:

  • BITOP AND destkey key [key ...] ,對一個或多個 key 求邏輯並,並將結果保存到 destkey

  • BITOP OR destkey key [key ...] ,對一個或多個 key 求邏輯或,並將結果保存到 destkey

  • BITOP XOR destkey key [key ...] ,對一個或多個 key 求邏輯異或,並將結果保存到 destkey

  • BITOP NOT destkey key ,對給定 key 求邏輯非,並將結果保存到 destkey

除了 NOT 操做以外,其餘操做均可以接受一個或多個 key 做爲輸入。

處理不一樣長度的字符串

BITOP 處理不一樣長度的字符串時,較短的那個字符串所缺乏的部分會被看做 0

空的 key 也被看做是包含 0 的字符串序列。

返回值:

保存到 destkey 的字符串的長度,和輸入 key 中最長的字符串長度相等。

示例:
coderknock> SETBIT bits-1 0 1
(integer) 0
coderknock> SETBIT bits-1 3 1
(integer) 0
# bits-1 爲 1001

coderknock> SETBIT bits-2 0 1
(integer) 0
coderknock> SETBIT bits-2 1 1
(integer) 0
coderknock> SETBIT bits-2 3 1
(integer) 0
# bits-2 爲 1011

#bits-1 bits-2 作 並 操做
coderknock> BITOP AND and-result bits-1 bits-2
(integer) 1
coderknock> GETBIT and-result 0
(integer) 1
coderknock> GETBIT and-result 1
(integer) 0
coderknock> GETBIT and-result 2
(integer) 0
coderknock> GETBIT and-result 3
(integer) 1
#and-result 1001

#bits-1 bits-2 作 或 操做
coderknock> BITOP OR or-result bits-1 bits-2
(integer) 1
coderknock> GETBIT and-result 0
(integer) 1
coderknock> GETBIT and-result 1
(integer) 0
coderknock> GETBIT and-result 2
(integer) 0
coderknock> GETBIT and-result 3
(integer) 1
#or-result 1011

# 非 操做只能針對一個 key
coderknock> BITOP NOT not-result bits-1 bits-2
(error) ERR BITOP NOT must be called with a single source key.
coderknock> BITOP NOT not-result bits-1
(integer) 1
coderknock> GETBIT not-result 0
(integer) 0
coderknock> GETBIT not-result 1
(integer) 1
coderknock> GETBIT not-result 2
(integer) 1
coderknock> GETBIT not-result 3
(integer) 0
# not-result 0110

# 異或操做
coderknock> BITOP XOR xor-result bits-1 bits-2
(integer) 1
coderknock> GETBIT xor-result 0
(integer) 0
coderknock> GETBIT xor-result 1
(integer) 1
coderknock> GETBIT xor-result 2
(integer) 0
coderknock> GETBIT xor-result 3
(integer) 0
# xor-result 0010複製代碼

BITOP 的複雜度爲 O(N) ,當處理大型矩陣(matrix)或者進行大數據量的統計時,最好將任務指派到附屬節點(slave)進行,避免阻塞主節點。

計算Bitmaps中第一個值爲 bit 的偏移量

BITPOS

自2.8.7可用。

時間複雜度:O(N)。

語法:BITPOS key bit [start][end]
說明:

返回字符串裏面第一個被設置爲 1 或者 0 的bit位。

返回一個位置,把字符串當作一個從左到右的字節數組,第一個符合條件的在位置 0,其次在位置 8,等等。

GETBITSETBIT 類似的也是操做字節位的命令。

默認狀況下整個字符串都會被檢索一次,只有在指定 start 和 end 參數(指定start和end位是可行的),該範圍被解釋爲一個字節的範圍,而不是一系列的位。因此start=0 而且 end=2是指前三個字節範圍內查找。

注意,返回的位的位置始終是從 0 開始的,即便使用了 start 來指定了一個開始字節也是這樣。

GETRANGE 命令同樣,start 和 end 也能夠包含負值,負值將從字符串的末尾開始計算,-1是字符串的最後一個字節,-2是倒數第二個,等等。

不存在的key將會被當作空字符串來處理。

返回值:

命令返回字符串裏面第一個被設置爲 1 或者 0 的 bit 位。

若是咱們在空字符串或者 0 字節的字符串裏面查找 bit 爲1的內容,那麼結果將返回-1。

若是咱們在字符串裏面查找 bit 爲 0 並且字符串只包含1的值時,將返回字符串最右邊的第一個空位。若是有一個字符串是三個字節的值爲 0xff 的字符串,那麼命令 BITPOS key 0 將會返回 24,由於 0-23 位都是1。

基本上,咱們能夠把字符串當作右邊有無數個 0。

然而,若是你用指定 start 和 end 範圍進行查找指定值時,若是該範圍內沒有對應值,結果將返回 -1。

示例:
redis> SET mykey "\xff\xf0\x00"
OK
redis> BITPOS mykey 0 # 查找字符串裏面bit值爲0的位置
(integer) 12
redis> SET mykey "\x00\xff\xf0"
OK
redis> BITPOS mykey 1 0 # 查找字符串裏面bit值爲1從第0個字節開始的位置
(integer) 8
redis> BITPOS mykey 1 2 # 查找字符串裏面bit值爲1從第2個字節(12)開始的位置
(integer) 16
redis> set mykey "\x00\x00\x00"
OK
redis> BITPOS mykey 1 # 查找字符串裏面bit值爲1的位置
                    (integer) -1複製代碼

BITFIELD

自3.2.0可用。

時間複雜度:每一個子命令的複雜度爲 O(1) 。

語法:BITFIELD key [GET type offset][SET type offset value][INCRBY type offset increment][OVERFLOW WRAP|SAT|FAIL]
說明:

BITFIELD key GET type offset INCRBY type offset increment

`BITFIELD` 命令能夠將一個 Redis 字符串看做是一個由二進制位組成的數組, 並對這個數組中儲存的長度不一樣的整數進行訪問 (被儲存的整數無需進行對齊)。 換句話說, 經過這個命令, 用戶能夠執行諸如 「對偏移量 1234 上的 5 位長有符號整數進行設置」、 「獲取偏移量 4567 上的 31 位長無符號整數」等操做。 此外, `BITFIELD` 命令還能夠對指定的整數執行加法操做和減法操做, 而且這些操做能夠經過設置妥善地處理計算時出現的溢出狀況。
複製代碼

BITFIELD 命令能夠在一次調用中同時對多個位範圍進行操做: 它接受一系列待執行的操做做爲參數, 並返回一個數組做爲回覆, 數組中的每一個元素就是對應操做的執行結果。

好比如下命令就展現瞭如何對位於偏移量 100 的 8 位長有符號整數執行加法操做, 並獲取位於偏移量 0 上的 4 位長無符號整數:

coderknock> BITFIELD mykey INCRBY i8 100 1 GET u4 0
1) (integer) 1
2) (integer) 0複製代碼

注意:

  • 使用 GET 子命令對超出字符串當前範圍的二進制位進行訪問(包括鍵不存在的狀況), 超出部分的二進制位的值將被當作是 0 。

  • 使用 SET 子命令或者 INCRBY 子命令對超出字符串當前範圍的二進制位進行訪問將致使字符串被擴大, 被擴大的部分會使用值爲 0 的二進制位進行填充。 在對字符串進行擴展時, 命令會根據字符串目前已有的最遠端二進制位, 計算出執行操做所需的最小長度。

支持的子命令以及數字類型

如下是 BITFIELD 命令支持的子命令:

  • GET <type> <offset> —— 返回指定的二進制位範圍。

  • SET <type> <offset> <value> —— 對指定的二進制位範圍進行設置,並返回它的舊值。

  • INCRBY <type> <offset> <increment> —— 對指定的二進制位範圍執行加法操做,並返回它的舊值。用戶能夠經過向 increment 參數傳入負值來實現相應的減法操做。

除了以上三個子命令以外, 還有一個子命令, 它能夠改變以後執行的 INCRBY 子命令在發生溢出狀況時的行爲:

  • OVERFLOW [WRAP|SAT|FAIL]

當被設置的二進制位範圍值爲整數時, 用戶能夠在類型參數的前面添加 i 來表示有符號整數, 或者使用 u 來表示無符號整數。 好比說, 咱們可使用 u8 來表示 8 位長的無符號整數, 也可使用 i16 來表示 16 位長的有符號整數。

BITFIELD 命令最大支持 64 位長的有符號整數以及 63 位長的無符號整數, 其中無符號整數的 63 位長度限制是因爲 Redis 協議目前還沒法返回 64 位長的無符號整數而致使的。

二進制位和位置偏移量

在二進制位範圍命令中, 用戶有兩種方法來設置偏移量:

  • 若是用戶給定的是一個沒有任何前綴的數字, 那麼這個數字指示的就是字符串以零爲開始(zero-base)的偏移量。

  • 另外一方面, 若是用戶給定的是一個帶有 # 前綴的偏移量, 那麼命令將使用這個偏移量與被設置的數字類型的位長度相乘, 從而計算出真正的偏移量。

好比說, 對於如下這個命令來講:

BITFIELD mystring SET i8 #0 100 i8 #1 200
複製代碼

命令會把 mystring 鍵裏面, 第一個 i8 長度的二進制位的值設置爲 100 , 並把第二個 i8 長度的二進制位的值設置爲 200 。 當咱們把一個字符串鍵當成數組來使用, 而且數組中儲存的都是同等長度的整數時, 使用 # 前綴可讓咱們免去手動計算被設置二進制位所在位置的麻煩。

溢出控制

用戶能夠經過 OVERFLOW 命令以及如下展現的三個參數, 指定 BITFIELD 命令在執行自增或者自減操做時, 碰上向上溢出(overflow)或者向下溢出(underflow)狀況時的行爲:

  • WRAP : 使用迴繞(wrap around)方法處理有符號整數和無符號整數的溢出狀況。 對於無符號整數來講, 迴繞就像使用數值自己與可以被儲存的最大無符號整數執行取模計算, 這也是 C 語言的標準行爲。 對於有符號整數來講, 上溢將致使數字從新從最小的負數開始計算, 而下溢將致使數字從新從最大的正數開始計算。 好比說, 若是咱們對一個值爲 127i8 整數執行加一操做, 那麼將獲得結果 -128

  • SAT : 使用飽和計算(saturation arithmetic)方法處理溢出, 也便是說, 下溢計算的結果爲最小的整數值, 而上溢計算的結果爲最大的整數值。 舉個例子, 若是咱們對一個值爲 120i8 整數執行加 10計算, 那麼命令的結果將爲 i8 類型所能儲存的最大整數值 127 。 與此相反, 若是一個針對 i8 值的計算形成了下溢, 那麼這個 i8 值將被設置爲 -127

  • FAIL : 在這一模式下, 命令將拒絕執行那些會致使上溢或者下溢狀況出現的計算, 並向用戶返回空值表示計算未被執行。

須要注意的是, OVERFLOW 子命令只會對緊隨着它以後被執行的 INCRBY 命令產生效果, 這一效果將一直持續到與它一同被執行的下一個 OVERFLOW 命令爲止。 在默認狀況下, INCRBY 命令使用 WRAP 方式來處理溢出計算。

如下是一個使用 OVERFLOW 子命令來控制溢出行爲的例子:

coderknock> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 1
2) (integer) 1

coderknock> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 2
2) (integer) 2

coderknock> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 3
2) (integer) 3

coderknock> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 0  -- 使用默認的 WRAP 方式處理溢出
2) (integer) 3  -- 使用 SAT 方式處理溢出複製代碼

而如下則是一個由於 OVERFLOW FAIL 行爲而致使子命令返回空值的例子:

coderknock> BITFIELD mykey OVERFLOW FAIL incrby u2 102 1
1) (nil)複製代碼
做用

BITFIELD 命令的做用在於它可以將不少小的整數儲存到一個長度較大的位圖中, 又或者將一個很是龐大的鍵分割爲多個較小的鍵來進行儲存, 從而很是高效地使用內存, 使得 Redis 可以獲得更多不一樣的應用 —— 特別是在實時分析領域: BITFIELD 可以以指定的方式對計算溢出進行控制的能力, 使得它能夠被應用於這一領域。

性能注意事項

BITFIELD 在通常狀況下都是一個快速的命令, 須要注意的是, 訪問一個長度較短的字符串的遠端二進制位將引起一次內存分配操做, 這一操做花費的時間可能會比命令訪問已有的字符串花費的時間要長。

二進制位的排列

BITFIELD 把位圖第一個字節偏移量 0 上的二進制位看做是 most significant 位, 以此類推。 舉個例子, 若是咱們對一個已經預先被所有設置爲 0 的位圖進行設置, 將它在偏移量 7 的值設置爲 5 位無符號整數值 23 (二進制位爲 10111 ), 那麼命令將生產出如下這個位圖表示:

+--------+--------+
|00000001|01110000|
+--------+--------+
複製代碼

當偏移量和整數長度與字節邊界進行對齊時, BITFIELD 表示二進制位的方式跟大端表示法(big endian)一致, 可是在沒有對齊的狀況下, 理解這些二進制位是如何進行排列也是很是重要的。

返回值:

若是 member 元素是集合的成員,返回 1

若是 member 元素不是集合的成員,或 key 不存在,返回 0

示例:
coderknock> SISMEMBER saddTest add1
(integer) 1
# add7 元素不存在
coderknock> SISMEMBER saddTest add7
(integer) 0
# key 不存在
coderknock> SISMEMBER nonSet a
(integer) 0
# key 類型不是集合
coderknock> SISMEMBER embstrKey a
(error) WRONGTYPE Operation against a key holding the wrong kind of value    複製代碼

案例

使用場景一:用戶簽到

Jedis redis = new Jedis("192.168.31.89",6379,100000);
//用戶uid
String uid = "1";
String cacheKey = "sign_"+Integer.valueOf(uid);
//記錄有uid的key
// $cacheKey = sprintf("sign_%d", $uid);

//開始有簽到功能的日期
String startDate = "2017-01-01";

//今天的日期
String todayDate = "2017-01-21";

//計算offset(時間搓)
long startTime = dateParase(startDate,"yyyy-MM-dd").getTime();
long todayTime = dateParase(todayDate,"yyyy-MM-dd").getTime();
long offset = (long) Math.floor((todayTime - startTime) / 86400);

System.out.println("今天是第"+offset+"天");

//簽到
//一年一個用戶會佔用多少空間呢?大約365/8=45.625個字節,好小,有木有被驚呆?
redis.setbit(cacheKey,offset,"1");

//查詢簽到狀況
boolean bitStatus = redis.getbit(cacheKey, offset);
//判斷是否已經簽到
//計算總簽到次數
long qdCount = redis.bitcount(cacheKey);複製代碼

使用場景二:統計活躍用戶

 使用時間做爲cacheKey,而後用戶ID爲offset,若是當日活躍過就設置爲1 那麼我該若是計算某幾天/月/年的活躍用戶呢(暫且約定,統計時間內只有有一天在線就稱爲活躍),有請下一個redis的命令 命令 BITOP operation destkey key [key ...] 說明:對一個或多個保存二進制位的字符串 key 進行位元操做,並將結果保存到 destkey 上。 說明:BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 這四種操做中的任意一種參數 

Map<String,List<Integer>>dateActiveuser = new HashMap<>();
Jedis redis = new Jedis("192.168.31.89",6379,100000);
Integer[] temp01 = {1,2,3,4,5,6,7,8,9,10};
List<Integer>temp01List = new ArrayList<>();
Collections.addAll(temp01List,temp01);
dateActiveuser.put("2017-01-10",temp01List);


Integer[] temp02 = {1,2,3,4,5,6,7,8};
List<Integer>temp02List = new ArrayList<>();
Collections.addAll(temp02List,temp02);
dateActiveuser.put("2017-01-11",temp02List);

Integer[] temp03 = {1,2,3,4,5,6};
List<Integer>temp03List = new ArrayList<>();
Collections.addAll(temp03List,temp03);
dateActiveuser.put("2017-01-12",temp03List);

Integer[] temp04 = {1,4,5,6};
List<Integer>temp04List = new ArrayList<>();
Collections.addAll(temp04List,temp04);
dateActiveuser.put("2017-01-13",temp04List);

Integer[] temp05 = {1,4,5,6};
List<Integer>temp05List = new ArrayList<>();
Collections.addAll(temp05List,temp05);
dateActiveuser.put("2017-01-14",temp05List);

String date[] = {"2017-01-10","2017-01-11","2017-01-12","2017-01-13","2017-01-14"};

//測試數據放入redis中
for (int i=0;i<date.length;i++){
    for (int j=0;j<dateActiveuser.get(date[i]).size();j++){
        redis.setbit(date[i], dateActiveuser.get(date[i]).get(j), "1");
    }
}

//bitOp
redis.bitop(BitOP.AND, "stat", "stat_2017-01-10", "stat_2017-01-11","stat_2017-01-12");

System.out.println("總活躍用戶:"+redis.bitcount("stat"));

redis.bitop(BitOP.AND, "stat1", "stat_2017-01-10", "stat_2017-01-11","stat_2017-01-14");
System.out.println("總活躍用戶:"+redis.bitcount("stat1"));

redis.bitop(BitOP.AND, "stat2", "stat_2017-01-10", "stat_2017-01-11");
System.out.println("總活躍用戶:"+redis.bitcount("stat2"));複製代碼
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息