bitmap是redis的一種擴展數據類型,主要用於二值狀態統計,好比公司記錄員工打卡記錄,電商網站記錄用戶登陸行爲,積分商城記錄用戶簽到狀況。redis
bigmap底層使用的是String的數據結構,而String保存在計算機中的格式是二進制的字節數組,這樣bitmap就充分利用了每一個字節的bit位,大大節省了內存開銷。數組
下面咱們看一下bitmap的使用。markdown
假如一個公司有100個員工,公司要對員工11月份的打卡行爲進行統計,咱們能夠爲11月份每一天分配一個bitmap,這個bitmap保存100個bit位,來記錄員工的打卡行爲。數據結構
咱們定義bitmap的key格式爲:signed:20201101,記錄2020年11月1日的打卡狀況。下面代碼是員工打卡和查詢員工打卡狀況:ide
/** * SETBIT命令 * 員工打卡 * 時間複雜度:O(1) */ public void sign(String key, int employeeNumber){ redisTemplate.opsForValue().setBit(key, employeeNumber - 1, true); } /** * GETBIT命令 * 查看員工打卡狀況 * 時間複雜度:O(1) */ public boolean isSigned(String key,int employeeNumber){ return redisTemplate.opsForValue().getBit(key, employeeNumber - 1); }
咱們能夠查看某一天的打卡總人數,代碼以下,入參:"signed:20201101":測試
/** * BITCOUNT命令 * 查看某一天的打卡人數 * 時間複雜度:O(N) */ public Long signedCount(String key){ return (Long) redisTemplate.execute((RedisCallback<Long>) conn -> conn.bitCount(key.getBytes())); }
這樣咱們就能根據打卡人數來判斷當天的遲到人數比例。網站
注意:上面的sign方法必須設置key的序列化採用StringRedisSerializer,不然查詢打卡狀況是查不到的。若是不設置StringRedisSerializer,上面的sign和isSigned改成使用conn來執行,代碼以下:code
public void sign(String key, int employeeNumber){ redisTemplate.execute((RedisCallback<Boolean>) conn -> conn.setBit(key.getBytes(), employeeNumber - 1, true)); } public boolean isSigned(String key, int employeeNumber){ return redisTemplate.execute((RedisCallback<Boolean>) conn -> conn.getBit(key.getBytes(), employeeNumber - 1)); }
或者使用下面代碼來設置RedisTemplate的setKeySerializer:內存
redisTemplate.setKeySerializer(new StringRedisSerializer());
那若是想看當月沒有遲到過的員工呢?這個時候就要用到交集了,對當月天天的bitmap作交集,值爲1的員工就是沒有遲到過的。get
這時就要用到bitmap的聚合運算了,命令BITOP, 支持AND(與)、OR(或), XOR(異或) and NOT(非)運算,除了NOT後面跟一個bitmap外,其餘3種聚合運算後面均可以跟多個bitmap,命令以下:
BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN BITOP NOT destkey srckey
爲了讓demo簡單一些,我這裏給出一個查看2天內沒有遲到的員工,代碼以下:
/** * 命令:BITOP * 複雜度:O(N) * 整個月全勤的員工數量,這裏用2天表明整個月 * @param key1 第一天 * @param key2 次日 */ public Long signedAllMonth(String key1, String key2){ String andMap = "signedAllMonth11"; redisTemplate.execute((RedisCallback) conn -> conn.bitOp(RedisStringCommands.BitOperation.AND, andMap.getBytes(), key1.getBytes(), key2.getBytes())); return (Long) redisTemplate.execute((RedisCallback<Long>) conn -> conn.bitCount(andMap.getBytes())); }
下面我再給出一段測試代碼,這段代碼模擬有50個員工全勤,bitMapService是我上面的代碼所在類:
@Test public void testSignedAllMonth(){ for (int i = 1; i <= 100; i++){ bitMapService.sign("signed:20201101", i); } for (int i = 1; i <= 100; i += 2){ bitMapService.sign("signed:20201102", i); } Long count = bitMapService.signedAllMonth("signed:20201101", "signed:20201102"); System.out.println("=========="+count); }
輸出以下:
==========50
好比網站有10萬個用戶,咱們要判斷當天的日活用戶。這樣咱們建立一個長度爲10萬的bitmap,每一個用戶id佔一個位,咱們定義key爲:user:login,number爲用戶編號。當有用戶登陸時,調用下面的方法:
redisTemplate.opsForValue().setBit(key, number - 1, true);
日終的時候,咱們用下面的方法就能夠判斷出日活用戶:
redisTemplate.execute((RedisCallback<Long>) conn -> conn.bitCount(key.getBytes()));
bitmap普遍地運用在二值計算的場景,對於一個二值狀態只用一個bit位就能夠,很是節約內存。好比咱們對一個10億的用戶進行日活計算,佔用的空間只有120M:
10億/8/1024/1024=120M
官網連接:
https://redis.io/commands/bitop
感謝閱讀,歡迎點贊,歡迎在看,更歡迎分享給須要的朋友。