首頁  >  文章  >  資料庫  >  一文帶你了解redis中的點陣圖(bitmap)

一文帶你了解redis中的點陣圖(bitmap)

青灯夜游
青灯夜游轉載
2021-12-16 10:02:504226瀏覽

如果使用一個set來記錄當天活躍的用戶,當用戶量非常大時會浪費非常多的空間。因此redis提供了點陣圖(bitmap),讓使用者可以對每一位進行單獨操作。以下這篇文章就來帶大家了解redis中的點陣圖,希望對大家有幫助!

一文帶你了解redis中的點陣圖(bitmap)

點陣圖

位圖,也就是大量bit組成的一個資料結構(每個bit只能是0和1 ),主要適合在一些場景下,進行空間的節省,並有意義的記錄數據,

例如一些大量的bool類型的訪問,一個用戶365天的簽到記錄,簽到了是1,沒簽到是0,如果用普通的key/value進行存儲,當用戶量很大的時候,需要的存儲空間是很大的。

如果使用位圖進行存儲,一年365天,用365個bit就可以存儲,365個bit換算成46個字節(一個稍長的字符串),如此就節省了很多的儲存空間,

一文帶你了解redis中的點陣圖(bitmap)

位圖的本質其實是一個普通的字串,也就是byte數組,可以使用get/set直接取得和設定整個位圖的內容,也可以使用getbit/setbit 將byte陣列看成bit數組來處理。 【相關推薦:Redis影片教學

使用位元操作設定字串

正常設定字串都使用set指令,下面我們使用setbit設定一下位數組,最後以取得字串的形式獲取,

首先我們取得h、e兩個ASCII碼使用二進位的表示如下,

一文帶你了解redis中的點陣圖(bitmap)

可以看到h的二進位碼是01101000 , e的二進位碼是01100101,我們只需要注意bit是1的位置,然後進行setbit,

一文帶你了解redis中的點陣圖(bitmap)

需要注意的是,位數組的順序和字元的位元順序是反的,根據這個原則,我們算出h字元每個1的位置分別是1/2/4, e字元的則是9/10/13/15 ,

一文帶你了解redis中的點陣圖(bitmap)

所以我們將使用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

零存整取

一文帶你了解redis中的點陣圖(bitmap)

#最後直接get data這個key,會發現剛好得到he,

一文帶你了解redis中的點陣圖(bitmap)

setbit get 的組合稱為零存整取,零存就是一個bit一個bit的設置,整取就是透過key名字,直接get出來所有的數據,

同樣,我們還可以進行零存零取,整存零取,整存就是直接使用字串設定整個位數組,零取則是透過bit的位置,進行bit的獲取。

零存零取

可以看到,我們根據setbit,對key叫做w的位數組進行bit設置,只設定了1/2/4這3個位置的值為1,下圖中有getbit w 3, 取得第三個位置的值,此時預設是0,如果從業務角度觸發,可以理解為,一共簽到4天,第三天沒有進行簽到,

一文帶你了解redis中的點陣圖(bitmap)

整存零取

下午所示,我們對w的這個key,直接set了一個h字符,隨後通過getbit獲取w的位數組裡的每個bit,可以看到獲取出來的內容和上面h字符的二進制內容相同1/2/4的位置是1,其餘是0

一文帶你了解redis中的點陣圖(bitmap)

注意

  • #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    

OK

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 做一些操作

一文帶你了解redis中的點陣圖(bitmap)

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

一文帶你了解redis中的點陣圖(bitmap)

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。

1一文帶你了解redis中的點陣圖(bitmap)

依然根据hello字符,来演示一下 incrby

127.0.0.1:6379> set w hello

OK

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

OK

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

OK

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中的點陣圖(bitmap)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除