共計 5114 個字符,預計需要花費 13 分鐘才能閱讀完成。
這篇文章將為大家詳細講解有關 redis 中的位圖是什么意思,丸趣 TV 小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
位圖
位圖,即大量 bit 組成的一個數據結構(每個 bit 只能是 0 和 1),主要適合在一些場景下,進行空間的節省,并有意義的記錄數據,
例如一些大量的 bool 類型的存取,一個用戶 365 天的簽到記錄,簽到了是 1,沒簽到是 0,如果用普通的 key/value 進行存儲,當用戶量很大的時候,需要的存儲空間是很大的。
如果使用位圖進行存儲,一年 365 天,用 365 個 bit 就可以存儲,365 個 bit 換算成 46 個字節(一個稍長的字符串),如此就節省了很多的存儲空間,
位圖的本質其實是一個普通的字符串,也就是 byte 數組,可以使用 get/set 直接獲取和設置整個位圖的內容,也可以使用 getbit/setbit 將 byte 數組看成 bit 數組來處理。【相關推薦:Redis 視頻教程】
使用位操作設置字符串
正常設置字符串都使用 set 命令,下面我們使用 setbit 設置一下位數組,最后以獲取字符串的形式獲取,
首先我們獲取一下 h、e 兩個 ASCII 碼使用二進制的表示如下,
可以看到 h 的二進制碼是 01101000 , e 的二進制碼是 01100101,我們只需要注意 bit 是 1 的位置,然后進行 setbit,
需要注意的是,位數組的順序和字符的位順序是反的,根據這個原則,我們算出 h 字符 每個 1 的位置分別是 1 /2/4, e 字符的則是 9/10/13/15,
所以我們將使用 setbit 設置一個位數組,并在每個位置上 (1/2/4/9/10/13/15) 設置對應的 1,
setbit data 1 1
setbit data 2 1
setbit data 4 1
setbit data 9 1
setbit data 10 1
setbit data 13 1
setbit data 15 1
零存整取
最后直接 get data 這個 key,會發現正好得到 he,
setbit + get 的組合稱為 零存整取,零存就是一個 bit 一個 bit 的設置,整取就是通過 key 名字,直接 get 出來所有的數據,
同樣,我們還可以進行 零存零取,整存零取,整存就是直接使用字符串設置整個位數組,零取則是通過 bit 的位置,進行 bit 的獲取。
零存零取
可以看到,我們根據 setbit,對 key 叫做 w 的位數組進行 bit 設置,只設置了 1 /2/ 4 這 3 個位置的值為 1,下圖中有 getbit w 3, 獲取第三個位置的值,此時默認是 0,如果從業務角度觸發,可以理解為,一共簽到 4 天,第三天沒有進行簽到,
整存零取
下午所示,我們對 w 的這個 key,直接 set 了一個 h 字符,隨后通過 getbit 獲取 w 的位數組里的每個 bit,可以看到獲取出來的內容和上面 h 字符的二進制內容相同 1/2/ 4 的位置是 1,其余是 0
注意
redis 的位數組是自動擴充的,如果設置的某個偏移位置超出了現有的內容范圍,就會自動將位數組進行零擴充,即擴容的位默認都是 0 值。
如果對應位的字節是不可打印字符,redis-cli 將會顯示該字符的十六進制形式。
一個字節是 8 個 bit(位),要區分字節和位。
統計和查找 (bitcount/bitpos)
redis 提供了 統計指令 bitcount 和 位圖查找指令 bitpos ,
bitcount 用來統計指定位置范圍內 1 的個數,bitpos 用來查找指定范圍內出現的第一個 0 或 1。
我們可以通過 bitcount 統計用戶一共簽到了多少天,通過 bitpos 指令查找用戶從哪一天開始第一次簽到,
如果指定了范圍參數[start, end],就可以統計在某個時間范圍內用戶簽到了多少天以及用戶自某天以后的哪天開始簽到,
但是需要注意的是,start 和 end 參數是字節索引,也就是說,指定的位范圍必須是 8 的倍數,
而不能任意指定,所以我們無法直接計算某個月內用戶簽到了多少天,如果需要計算的話,
可以使用 getrange 命令取出該月覆蓋的字節內容,然后在內存中進行統計,例如 2 月覆蓋了 10-12 個字節,就使用 getrange w 8 12。
127.0.0.1:6379 set w hello
127.0.0.1:6379 bitcount w # 所有字符中有多少個 1
(integer) 21
127.0.0.1:6379 bitcount w 0 0 # 第一個字符中 1 的位數
(integer) 3
127.0.0.1:6379 bitcount w 0 1 # 前兩個字符中 1 的位數
(integer) 7
127.0.0.1:6379 bitpos w 0 # 第一個 0 位
(integer) 0
127.0.0.1:6379 bitpos w 1 # 第一個 1 位
(integer) 1
127.0.0.1:6379 bitpos w 1 1 1 # 從第二個字符算起,第一個 1 位
(integer) 9
127.0.0.1:6379 bitpos w 1 2 2 # 從第三個字符算起,第一個 1 位
(integer) 17
bitfield
之前介紹的 setbit / getbit 指定位的值都是單個位,如果要一次操作多個位,就必須使用管道來處理,
在 redis3.2 以后,提供了 bitfield 指令,可以一次對多個位進行操作,bitfield 有三個子指令,分別是 get/set/incrby, 都可以對指定位片段進行讀寫,
但是最多只能處理 64 個連續的位,如果超過 64 位,就需要使用多個子指令,bitfield 可以一次執行多個子指令。
示例
下面對下圖的位數組使用 bitfield 做一些操作
127.0.0.1:6379 bitfield w get u4 0 # 從第 1 個位開始取 4 個位,結果是無符號數(u)
1) (integer) 6
127.0.0.1:6379 bitfield w get u3 2 # 從第 3 個位開始取 3 個位,結果是無符號數(u)
1) (integer) 5
127.0.0.1:6379 bitfield w get i4 0 # 從第 1 個位開始取 4 個位,結果是有符號數(i)
1) (integer) 6
127.0.0.1:6379 bitfield w get i3 2 # 從第 3 個位開始取 3 個位,結果是有符號數(i)
1) (integer) -3
有符號數是指獲取的位數組中的第一個位是符號位,剩下的才是值,如果第一位是 1,就是負數,
無符號數表示非負數,沒有符號位,獲取的位數組全部都是值,有符號數最多可以獲取 64 位,
無符號數只能獲取 63 位,因為 redis 協議中的 integer 是有符號數,最大 64 位,不能傳遞 64 位的無符號值,
如果超出位數限制,redis 就會告訴你參數錯誤。
上面的指令可以合并成一條指令, 可以看到得到的結果是一樣的,
bitfield w get u4 0 get u3 2 get i4 0 get i3 2
set 修改
我們從第 9 個位開始,用 8 個無符號數替換已經存在的 8 個位,其實就是把第二個字符替換了,由 e 變成 a(它的 ASCII 碼是 97),可以看到結果也變成了 hallo
127.0.0.1:6379 bitfield w set u8 8 97
1) (integer) 101
127.0.0.1:6379 get w
hallo
incrby
incrby 對指定范圍的位進行自增操作,即 ++,這可能會發生溢出,如果增加了正數,會出現上溢出,如果增加的是負數,會出現下溢出,
redis 默認的處理是折返,即如果出現了溢出,就將溢出的符號位丟掉,例如,如果是 8 位無符號數 255,加 1 后就全部變成 0,如果是 8 位有符號數 127,加 1 后就溢出變成 -128。
依然根據 hello 字符,來演示一下 incrby
127.0.0.1:6379 set w hello
127.0.0.1:6379 bitfield w get u4 2 # 從第 3 位開始取 4 個無符號整數,第一次是 10
1) (integer) 10
127.0.0.1:6379 bitfield w incrby u4 2 1
1) (integer) 11
127.0.0.1:6379 bitfield w incrby u4 2 1
1) (integer) 12
127.0.0.1:6379 bitfield w incrby u4 2 1
1) (integer) 13
127.0.0.1:6379 bitfield w incrby u4 2 1
1) (integer) 14
127.0.0.1:6379 bitfield w incrby u4 2 1
1) (integer) 15
127.0.0.1:6379 bitfield w incrby u4 2 1 # 到這里的時候,已經溢出折返成 0 了
1) (integer) 0
bitfield 指令提供溢出策略子指令 overflow,用戶可以選擇溢出行為,默認是折返(wrap),還可以選擇失敗(fail) 即報錯不執行,還有飽和截斷(sat) 即超過范圍就停留在最大或最小值,
overflow 指令只影響接下來的第一條指令,這條指令執行完以后,溢出策略就會變成默認值 折返(wrap)。
飽和截斷
127.0.0.1:6379 set w hello
127.0.0.1:6379 bitfield w overflow sat incrby u4 2 1
1) (integer) 11
127.0.0.1:6379 bitfield w overflow sat incrby u4 2 1
1) (integer) 12
127.0.0.1:6379 bitfield w overflow sat incrby u4 2 1
1) (integer) 13
127.0.0.1:6379 bitfield w overflow sat incrby u4 2 1
1) (integer) 14
127.0.0.1:6379 bitfield w overflow sat incrby u4 2 1
1) (integer) 15
127.0.0.1:6379 bitfield w overflow sat incrby u4 2 1 # 接下來的都將是保持最大值
1) (integer) 15
127.0.0.1:6379 bitfield w overflow sat incrby u4 2 1
1) (integer) 15
127.0.0.1:6379 bitfield w overflow sat incrby u4 2 1
1) (integer) 15
失敗不執行
127.0.0.1:6379 set w hello
127.0.0.1:6379 bitfield w overflow fail incrby u4 2 1
1) (integer) 11
127.0.0.1:6379 bitfield w overflow fail incrby u4 2 1
1) (integer) 12
127.0.0.1:6379 bitfield w overflow fail incrby u4 2 1
1) (integer) 13
127.0.0.1:6379 bitfield w overflow fail incrby u4 2 1
1) (integer) 14
127.0.0.1:6379 bitfield w overflow fail incrby u4 2 1
1) (integer) 15
127.0.0.1:6379 bitfield w overflow fail incrby u4 2 1 # 接下來的都是失敗
1) (nil)
127.0.0.1:6379 bitfield w overflow fail incrby u4 2 1
1) (nil)
127.0.0.1:6379 bitfield w overflow fail incrby u4 2 1
1) (nil)
get/set/incrby 一起執行
127.0.0.1:6379 bitfield w set u4 1 0 get u4 1 incrby u4 2 1
1) (integer) 0
2) (integer) 0
3) (integer) 1
127.0.0.1:6379 get w
\x04ello
關于“redis 中的位圖是什么意思”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。