一、引言
? ? ? ? 用戶簽到功能是很多應用都離不開的一個板塊,單詞打開、QQ達人等等為我們所熟知,這項功能該如何實現呢,一些朋友可能想當然的覺得無非將每日的簽到數據記錄下來不就好了,不會去細想用誰記錄,如何記錄才合適。
????????假如我們計入傳統的關系型數據庫,以MySQl為例,我們分別用INT、TINYINT、DATE分別存儲用戶ID、是否簽到(0或1)、當天日期,那么每條記錄將占用8字節(4+1+3),當用戶達到一定規模,每月的簽到數據存儲將占用很大空間,統計查找效率也低下。
? ? ? ? 為了解決這一問題,我們引入今天要介紹的一種Redis中的數據結構BitMap(位圖)。
二、簡介及基本操作
1.簡介
Redis 的 Bitmap(位圖)是一種基于位操作的數據結構,底層實際上是字符串(String)類型,但可以將字符串視為一個由二進制位組成的數組。每個位只能是 0 或 1,因此 Bitmap 非常適合用于存儲和處理大量的布爾狀態信息,而且非常節省空間。
2.基本操作
-
SETBIT:設置指定偏移量(offset)上的位的值(0 或 1)。
-
GETBIT:獲取指定偏移量上的位的值。
-
BITCOUNT:計算指定范圍內值為 1 的位的數量。
-
BITOP:對多個 Bitmap 進行位運算(AND、OR、XOR、NOT),并將結果存儲到新的 Bitmap 中。
-
BITPOS:查找指定范圍內第一個值為 0 或 1 的位的位置。
?關于位圖的詳細命令及RedisTemplate的詳細內容大家可以自行了解。
三、簽到實現
整個操作還是比較簡單的,我們在收到簽到請求后獲取到用戶信息,時間數據拼接為key對用戶當月的簽到操作做記錄,即在位圖對應位數上做寫1操作(controller比較簡單大家自己隨手寫一個測試即可)
@Overridepublic void sign() {//獲取用戶Long uid = UserHolder.getUser().getId();//獲取日期LocalDateTime now = LocalDateTime.now();//拼接keyString keySuffix= now.format(DateTimeFormatter.ofPattern(":yyyyMM"));String key=RedisConstants.USER_SIGN_KEY+uid+keySuffix;//本月第幾天int dayOfMonth = now.getDayOfMonth();//寫入redis,位圖是從0開始索引的,所以減一stringRedisTemplate.opsForValue().setBit(key,dayOfMonth-1,true);}
?接下來我們用PostMan 做一下簽到測試
可以看到Redis中已經存儲了當前用戶三月份的簽到數據,也就是今天(03-31)的簽到,BitMap第31位為1,代表03-31以簽到,并且這一月的數據僅占4B的空間。
四、連續簽到統計實現
? ? ? ? 這里的連續簽到即指當月中從當前天起往回計數,直到未簽到的日子的總數,即今天沒簽那就算斷了(如果要統計當月簽到總數的話自然可以用bitCount直接統計)。
????????那怎么對BitMap進行這種倒敘的計數統計呢,其實我們從其二進制的存儲結構就能看出端倪,我們直接用BitMap數據和1進行與運算判斷當前的最后一位是否為1,條件滿足則計數并且無符號右移一位,繼續對當前最后一位做判斷直到不滿足條件。
public Integer signCount() {//獲取用戶Long uid = UserHolder.getUser().getId();//獲取日期LocalDateTime now = LocalDateTime.now();//拼接keyString keySuffix= now.format(DateTimeFormatter.ofPattern(":yyyyMM"));String key=RedisConstants.USER_SIGN_KEY+uid+keySuffix;//本月第幾天int dayOfMonth = now.getDayOfMonth();//截至今天的位圖簽到數據List<Long> result = stringRedisTemplate.opsForValue().bitField(key,BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));if (result == null||result.isEmpty()) {return 0;}Long num = result.get(0);if (num == null||num==0) {return 0;}int count=0;//循環遍歷while (true){//和1做與運算,得到最后一個比特位,再和0比較if ((num & 1 ) == 0) {//為零,未簽到break;}else {//為1繼續計數count++;}//無符號右移1位,切換下一比特位num>>>=1;}return count;}
?我們改動一下剛剛的BitMap數據,為了方便我就不用BitField命令了,直接用工具改成如下數據
然后再用PostMan做測試,得到的連續簽到天數也是4天
????????本次分享主要為大家介紹一下BitMap在這種簽到業務中的應用,比較簡單,到這里已經全部結束,感謝大家閱讀。