Redis學習手冊7—數據結構之位圖

Redis的位圖

Redis的位圖(bitmap)是由多個二進制位組成的數組,數組中的每個二進制位都有與之對應的偏移量(也稱索引),用戶通過這些偏移量可以對位圖中指定的一個或多個二進制位進行操作。

下圖展示了一個包含8個二進制位的位圖示例,這個位圖存儲的值爲 10010100:
在這裏插入圖片描述

Redis位圖功能

Redis爲位圖提供了一系列操作命令,通過這些命令,用戶可以:

  • 爲位圖指定偏移量上的二進制位設置值,或者獲取位圖指定偏移量上的二進制位的值。
  • 統計位圖中有多少個二進制位被設置成了 1 。
  • 查找位圖中第一個被設置爲指定值的二進制爲並返回它的偏移量。
  • 對一個或多個位圖執行邏輯並邏輯或邏輯異或以及邏輯非運算。
  • 將指定類型的整數存儲到位圖中。

位圖命令速查表

下表列舉了Redis爲位圖類型鍵提供的一系列命令,用法及參數以及它們的簡要說明:

命令 用法及參數 說明
SETBIT SETBIT bitmap offset value 爲位圖指定偏移量上的二進制位設置值
GETBIT GETBIT bitmap offset 獲取指定偏移量上的二進制位的值
BITCOUNT BITCOUNT bitmap [start end] 統計位圖中值爲 1 的二進制位數量
BITPOS BITPOS bitmap value [start end] 查找第一個指定值的二進制位,返回該二進制位的偏移量
BITOP BITOP operation result_key bitmap [bitmap ...] 對一個或多個位圖執行指定的二進位運算,並將結果保存到指定的鍵中
BITFIELD BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL] 在位圖中存儲整數值

命令詳解

SETBIT命令

通過使用SETBIT命令,可以爲位圖指定偏移量上的二進制位設置值:

SETBIT bitmap offset value

SETBIT命令在成功設置之後,將返回二進制位被設置值之前的舊值作爲返回值。

127.0.0.1:6379> SETBIT bitmap001 0 1
(integer) 0  -- 二進制位原來的值爲 0
127.0.0.1:6379> SETBIT bitmap001 3 1
(integer) 0
127.0.0.1:6379> SETBIT bitmap001 5 1
(integer) 0

下圖展示了上述命令的執行過程:
在這裏插入圖片描述

位圖的擴展

當用戶執行SETBIT命令嘗試對一個位圖進行設置的時候,如果位圖不存在,或者位圖當前的大小無法滿足用戶想要執行的設置操作,那麼Redis將對被設置的位圖進行擴展,使得位圖可以滿足用戶的設置請求。

因爲Redis對位圖的擴展操作是以字節爲單位進行的,所以擴展之後的位圖包含的二進制位數量可能會比用戶要求的稍微多一些,並且在擴展位圖的同時,Redis還會將所有未被設置的二進制位的值初始化爲 0 。

比如執行以下命令,對尚未存在的位圖bitmap002在偏移量10之上的二進制位進行設置:

127.0.0.1:6379> SETBIT bitmap002 10 1
(integer) 0

那麼Redis創建出的位圖並不會只有11個二進制位,而是有兩個字節共16個二進制位,如下圖所示:
在這裏插入圖片描述
上圖可以看出,除了偏移量10之上的二進制位,其他未被設置的二進制位默認都是 0 。

注意: 與其他Redis命令可以使用負數作爲偏移量的做法不同,SETBIT命令只能使用正數偏移量,嘗試輸入負數將引發錯誤。

複雜度:O(1)O(1)
版本要求:SETBIT命令從Redis 2.2.0版本開始可用。

GETBIT命令

使用GETBIT命令可以獲取指定偏移量上的二進制的值:

GETBIT bitmap offset

SETBIT命令一樣,GETBIT命令只接受正數偏移量。

127.0.0.1:6379> GETBIT bitmap001 0
(integer) 1

如果輸入的偏移量超過了位圖目前擁有的最大偏移量,將返回 0 。

複雜度:O(1)O(1)
版本要求:GETBIT命令從Redis 2.2.0版本開始可用。

BITCOUNT命令

BITCOUNT命令可以用來統計位圖中值爲 1 的二進制位數量:

BITCOUNT bitmap
127.0.0.1:6379> BITCOUNT bitmap001
(integer) 3

在默認情況下,BITCOUNT命令將統計位圖中包含的所有字節中的二進制位,但是我們也可以通過可選範圍參數 startend來指定統計的字節範圍:

BITCOUNT bitmap [start end]

需要注意的是,startend參數與之前介紹的 offset參數並不相同,這兩個參數是用來指定 字節偏移量 而不是二進制位偏移量。

位圖的字節偏移量與Redis其他數據結構的偏移量一樣,都是從0開始的:位圖第一個字節的偏移量爲0,第二個字節的偏移量爲1,以此類推。

下圖所示爲bitmap003,使用以下命令來統計第一個字節裏有多少個二進制位被設置成了 1 :
在這裏插入圖片描述

127.0.0.1:6379> BITCOUNT bitmap003 0 0  -- 統計第一個字節裏的二進制位
(integer) 6
127.0.0.1:6379> BITCOUNT bitmap003 2 2  -- 統計第二個字節裏的二進制位
(integer) 4

BITCOUNT命令的startend參數的值除了可以是正數,也可以是負數,與Redis其他支持負數索引的命令類似。

複雜度:O(N)O(N),其中NN爲被統計的字節的數量。
版本要求:BITCOUNT命令從 Redis 2.6.0版本開始可用。

BITPOS命令

使用BITPOS命令可以查找位圖中第一個被設置爲指定值的二進制位,並返回該二進制位的偏移量:

BITPOS bitmap value

比如,通過執行以下命令,我們可以知道位圖bitmap003第一個被設置爲 1 的二進制位所在的偏移量:

127.0.0.1:6379> BITPOS bitmap003 1
(integer) 0   -- 位圖第一個被設置爲1的二進制位的偏移量爲 0

同樣的,在默認情況下,BITPOS命令的查找範圍將覆蓋位圖包含的所有二進制位,但是,我們也可以使用可選範圍參數 startend來指定需要查找的字節範圍:

BITPOS bitmap value [start end]

其中 startend的含義與BITCOUNT命令的一樣,指的都是位圖的字節偏移量,而不是二進制位偏移量,同樣支持負數偏移量。

對於不存在的位圖或者一個所有位都被設置爲0的位圖中查找值爲 1 的二進制位時,會返回 -1 。

127.0.0.1:6379> BITPOS not-exists-bitmap 1
(integer) -1
127.0.0.1:6379> BITPOS all-0-bitmap 1
(integer) -1

如果在一個所有位都被設置爲 1 的位圖中查找值爲 0 的二進制位,那麼 BITPOS命令將返回位圖最大偏移量加上 1 作爲結果。

127.0.0.1:6379> BITPOS bitmap-8bits-all-1  0
(integer) 8

複雜度:O(N)O(N),其中NN爲查找涉及的字節數量。
版本要求:BITPOS命令從 Redis 2.8.7版本開始可用。

BITOP命令

使用BITOP命令可以對一個或多個位圖執行指定的二進位運算,並將結果保存到指定的鍵中:

BITOP operation result_key bitmap [bitmap  ...]

其中 operation參數的值可以是:ANDORXORNOT中的任意一個,這4個值分別對應:邏輯並邏輯或邏輯異或邏輯非。其中ANDORXOR這3種運算允許用戶使用任意數量的位圖作爲輸入,而 NOT運算只允許使用一個位圖作爲輸入。BITOP命令將結果保存在指定的鍵中,會返回被存儲位圖的字節長度。

127.0.0.1:6379> BITOP AND and_result bitmap001 bitmap002 bitmap003
(integer) 1
127.0.0.1:6379> BITOP OR or_result bitmap001 bitmap002 bitmap003
(integer) 1
127.0.0.1:6379> BITOP XOR xor_result bitmap001 bitmap002 bitmap003
(integer) 1
127.0.0.1:6379> BITOP NOT not_result bitmap001
(integer) 1

BITOP命令在對兩個不同長度的位圖執行運算時,會將長度較短的那個位圖中不存在的二進制位的值看作是 0 。

複雜度:O(N)O(N),其中NN爲計算涉及的字節數總量。
版本要求:BITOP命令從Redis 2.6.0版本開始可用。

BITFIELD命令

BITFIELD命令允許用戶在位圖中的任意區域(field)存儲指定長度的整數值,並對這些整數執行加法或減法操作。
BITFIELD命令支持SETGETINCRBYOVERFLOW這4個子命令。

根據偏移量對區域進行設置

通過使用BITFIELD命令的SET子命令,用戶可以在位圖的指定偏移量offset上設置一個type類型的整數值value

BITFIELD bitmap SET type offset value

其中:

  • offset 參數用於指定設置的起始偏移量,這個偏移量從0開始計算,偏移量爲0表示設置從位圖的第一個二進制位開始。如果被設置的值長度不止一位,那麼設置將自動延伸至之後的二進制位。
  • type 參數用於指定被設置的值的類型,這個參數的值需要以iu爲前綴,後跟被設置值的位長度,其中i表示被設置的值爲有符號整數,u表示無符號整數。BITFIELD的各個子命令目前最大的能夠對64位長的有符號整數(i64)和63位長的無符號整數(u63)進行操作。
  • value參數用於指定被設置的整數值,這個值的類型應該與type參數指定的類型一致。如果給定的值長度超過了type參數指定的類型,那麼SET命令將根據type參數指定的類型截斷給定值。
127.0.0.1:6379> bitfield bitmap set u8 0 128
(integer) 0

BITFIELD命令允許用戶在一次調用中執行多個子命令,如下所示:

127.0.0.1:6379> BITFIELD bitmap SET u8 0 123 SET i32 20 10086 SET i64 188 123456789
1) (integer) 198
2) (integer) 0
3) (integer) 0

SET子命令的特點:

  • 設置可以在位圖的任意偏移量上進行,被設置的區域之間不必是連續的,也不需要進行對齊,未被設置的二進制位自動初始化爲0 。
  • 在同一個位圖中可以存儲多個不同類型的和不同長度的整數。

從節約資源的角度考慮,一般情況應該如下使用位圖:

  • 以對齊的方式使用位圖,並且讓位圖儘可能的緊湊,避免包含過多的空洞。
  • 每個位圖只存儲一種類型的整數,並使用int-8bitunsigned-16bit這樣的鍵名作爲前綴。

根據索引對區域進行設置

除了根據偏移量對位圖進行設置之外,SET子命令還允許用戶根據給定類型的長度,對位圖上指定索引上存儲的整數進行設置:

BITFIELD bitmap SET type #index value

當位圖中存儲的都是相同類型的整數時,使用這種設置方法將給用戶帶來非常大的便利,因爲這種方法允許用戶直接對位圖上
指定索引的整數進行設置,而不比知道整數值具體存儲在位圖的哪個偏移量上。

假設現在有一個位圖,存儲着多個8位長度的無符號整數,現在想要在它的第133個8位無符號整數的值設置爲22。如果使用SET子命令的偏移量設置格式,就需要先使用算是 (1331)8(133-1)*8 計算出第133個8位無符號整數在位圖中的起始偏移量1056,再執行以下命令:

127.0.0.1:6379> BITFIELD  bitmap  SET u8 1056  22

很明顯,這種手動計算偏移量然後進行設置的做法非常麻煩也容易出錯,下面是使用索引的方式:

127.0.0.1:6379> BITFIELD bitmap SET u8 #132 22

注意: 因爲SET子命令接受的索引是從0開始計算的,所以上面的子命令使用的索引是132,而不是133

獲取區域存儲的值

使用BITFIELD命令的GET子命令可以獲取指定區域存儲的整數值:

BITFIELD  bitmap  GET  type  offset   -- 通過偏移量獲取
BITFIELd bitmap GET type #index   -- 通過索引獲取
127.0.0.1:6379> BITFIELD bitmap GET u8 0
(integer) 128

執行加法或減法

除了設置和獲取整數值以外,BITFIELD命令還可以對位圖存儲的整數值執行加法或減法操作:

BITFIELD bitmap INCRBY type offset increment   -- 指定偏移量上的整數加法
BITFIELD bitmap INCRBY type #index increment  -- 指定索引上的整數加法

執行加法的操作與字符串類型鍵的INCRBY命令一樣,只要傳入負數增量就可以實現減法操作。

處理溢出

BITFIELD命令除了可以使用INCRBY子命令執行加法或減法操作以爲,還可以使用OVERFLOW子命令去控制INCRBY子命令在計算溢出時的行爲:

BITFIELD bitmap [...] OVERFLOW WRAP|SAT|FAIL [...]

OVERFLOW子命令的參數解釋如下:

參數名 說明
WRAP 表示使用迴繞方式處理溢出,這也是C語言默認的處理方式,在這一模式下,向上溢出的整數值將從類型的最小值開始重新計算,而向下溢出的整數值則會從類型的最大值開始重新計算
SAT 表示使用飽和運算方式處理溢出,在這一模式下,向上溢出的整數值將被設置爲類型的最大值,而向下溢出的整數值則被設置爲類型的最小值
FAIL 表示讓INCRBY子命令在檢測到計算會引發溢出時拒絕執行計算,並返回空值表示計算失敗

如果在執行BITFIELD命令時沒有指定OVERFLOW處理溢出的方式,那麼默認將使用WRAP方式處理計算溢出。

注意: 因爲OVERFLOW子命令只會對同一個BITFIELD命令調用中排在它後面的那些INCRBY子命令產生效果,因此用戶必須把OVERFLOW子命令放在它想要影響的INCRBY子命令之前。

使用位圖存儲整數的原因

在一般情況下,當用戶使用字符串或散列去存儲整數的時候,Redis都會爲被存儲的整數分配一個long類型的值,並使用對象去包裹這個值,然後再把對象關聯到數據庫或散列中。

與此相反,BITFIELD命令允許用戶自行指定被存儲數據的類型,並且不會使用對象去包裹這些整數,因此當我們想要存儲長度超過long類型的整數,並且希望儘可能的減少對象包裹帶來的內存消耗時,就可以考慮使用位圖來存儲整數。

複雜度:O(N)O(N),其中NN爲用戶給定的子命令數量。
版本要求:BITFIELD命令從Redis 3.2.0版本開始可用。

上一篇:Redis學習手冊6—數據結構之HyperLogLog

下一篇:Redis學習手冊8—數據結構之地理座標

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